summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp17
-rw-r--r--Android.bp3
-rw-r--r--api/Android.bp62
-rw-r--r--api/ApiDocs.bp2
-rw-r--r--api/api.go5
-rw-r--r--cmds/idmap2/libidmap2/ResourceContainer.cpp16
-rw-r--r--core/api/current.txt301
-rw-r--r--core/api/system-current.txt41
-rw-r--r--core/api/test-current.txt10
-rw-r--r--core/java/Android.bp37
-rw-r--r--core/java/android/adaptiveauth/OWNERS2
-rw-r--r--core/java/android/app/Activity.java22
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java5
-rw-r--r--core/java/android/app/AppOpsManager.java17
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java284
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig15
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.aidl21
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.java260
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java38
-rw-r--r--core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java4
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java44
-rw-r--r--core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java2
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java4
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java280
-rw-r--r--core/java/android/app/appfunctions/GenericDocumentWrapper.java11
-rw-r--r--core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl4
-rw-r--r--core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java42
-rw-r--r--core/java/android/app/assist/AssistContent.java15
-rw-r--r--core/java/android/app/multitasking.aconfig8
-rw-r--r--core/java/android/app/performance.aconfig17
-rw-r--r--core/java/android/content/Context.java6
-rw-r--r--core/java/android/content/res/flags.aconfig10
-rw-r--r--core/java/android/database/CursorWindow.java2
-rw-r--r--core/java/android/database/CursorWindow_ravenwood.java (renamed from ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java)20
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java59
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java40
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java72
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java115
-rw-r--r--core/java/android/hardware/display/DisplayManager.java24
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java26
-rw-r--r--core/java/android/hardware/display/DisplayTopology.aidl19
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java (renamed from services/core/java/com/android/server/display/DisplayTopology.java)229
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl10
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java8
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig15
-rw-r--r--core/java/android/hardware/location/ContextHubInfo.java1
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java38
-rw-r--r--core/java/android/hardware/location/HubInfo.aidl20
-rw-r--r--core/java/android/hardware/location/HubInfo.java153
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl5
-rw-r--r--core/java/android/hardware/location/VendorHubInfo.aidl20
-rw-r--r--core/java/android/hardware/location/VendorHubInfo.java95
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java2
-rw-r--r--core/java/android/os/ConcurrentMessageQueue/MessageQueue.java2
-rw-r--r--core/java/android/os/IVibratorManagerService.aidl9
-rw-r--r--core/java/android/os/LegacyMessageQueue/MessageQueue.java2
-rw-r--r--core/java/android/os/LockedMessageQueue/MessageQueue.java2
-rw-r--r--core/java/android/os/MessageQueue_ravenwood.java (renamed from ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java)13
-rw-r--r--core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java2
-rw-r--r--core/java/android/os/SystemVibrator.java70
-rw-r--r--core/java/android/os/SystemVibratorManager.java103
-rw-r--r--core/java/android/os/Vibrator.java69
-rw-r--r--core/java/android/os/VibratorManager.java37
-rw-r--r--core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl37
-rw-r--r--core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl33
-rw-r--r--core/java/android/os/instrumentation/MethodDescriptor.aidl37
-rw-r--r--core/java/android/os/instrumentation/TargetProcess.aidl29
-rw-r--r--core/java/android/os/vibrator/IVibrationSession.aidl55
-rw-r--r--core/java/android/os/vibrator/IVibrationSessionCallback.aidl43
-rw-r--r--core/java/android/os/vibrator/VendorVibrationSession.java236
-rw-r--r--core/java/android/permission/flags.aconfig3
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/security/flags.aconfig7
-rw-r--r--core/java/android/service/dreams/flags.aconfig10
-rw-r--r--core/java/android/service/settings/preferences/GetValueRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/GetValueRequest.java139
-rw-r--r--core/java/android/service/settings/preferences/GetValueResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/GetValueResult.java213
-rw-r--r--core/java/android/service/settings/preferences/IGetValueCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/IMetadataCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/ISetValueCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/ISettingsPreferenceService.aidl18
-rw-r--r--core/java/android/service/settings/preferences/MetadataRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/MetadataRequest.java75
-rw-r--r--core/java/android/service/settings/preferences/MetadataResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/MetadataResult.java164
-rw-r--r--core/java/android/service/settings/preferences/SetValueRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/SetValueRequest.java158
-rw-r--r--core/java/android/service/settings/preferences/SetValueResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/SetValueResult.java179
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java436
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceService.java201
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceServiceClient.java248
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceValue.java220
-rw-r--r--core/java/android/telephony/PhoneStateListener.java6
-rw-r--r--core/java/android/telephony/TelephonyCallback.java34
-rw-r--r--core/java/android/telephony/TelephonyRegistryManager.java19
-rw-r--r--core/java/android/view/DisplayInfo.java4
-rw-r--r--core/java/android/view/KeyEvent.java89
-rw-r--r--core/java/android/view/SurfaceControl.java2
-rw-r--r--core/java/android/view/View.java10
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java18
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig7
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig8
-rw-r--r--core/java/android/window/DesktopModeFlags.java3
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java2
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java (renamed from ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java)4
-rw-r--r--core/java/com/android/internal/os/LongMultiStateCounter.java2
-rw-r--r--core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java (renamed from ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java)4
-rw-r--r--core/java/com/android/internal/telephony/IPhoneStateListener.aidl2
-rw-r--r--core/java/com/android/internal/telephony/ITelephonyRegistry.aidl3
-rw-r--r--core/java/com/android/internal/widget/NotificationProgressDrawable.java49
-rw-r--r--core/jni/android_hardware_input_InputWindowHandle.cpp165
-rw-r--r--core/jni/android_hardware_input_InputWindowHandle.h21
-rw-r--r--core/jni/android_view_SurfaceControl.cpp12
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/Android.bp3
-rw-r--r--core/res/AndroidManifest.xml54
-rw-r--r--core/res/res/values/attrs_manifest.xml16
-rw-r--r--core/res/res/values/public-staging.xml4
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java79
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt472
-rw-r--r--core/tests/coretests/src/android/view/DisplayInfoTest.java17
-rw-r--r--core/tests/vibrator/src/android/os/VibratorTest.java20
-rw-r--r--data/etc/platform.xml2
-rw-r--r--data/etc/privapp-permissions-platform.xml6
-rw-r--r--data/keyboards/Generic.kl42
-rw-r--r--graphics/java/android/graphics/ColorSpace.java2
-rw-r--r--keystore/java/android/security/keystore/KeyStoreManager.java2
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java128
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java155
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt94
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java152
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java191
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java10
-rw-r--r--libs/appfunctions/Android.bp8
-rw-r--r--libs/appfunctions/api/current.txt54
-rw-r--r--libs/appfunctions/appfunctions.extension.xml (renamed from libs/appfunctions/appfunctions.sidecar.xml)4
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java211
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java)25
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java)31
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java)6
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java103
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java)74
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java359
-rw-r--r--libs/appfunctions/tests/Android.bp2
-rw-r--r--libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt (renamed from libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt)78
-rw-r--r--media/java/android/media/MediaCas.java9
-rw-r--r--media/java/android/media/MediaFormat.java12
-rw-r--r--media/java/android/media/audio/common/AidlConversion.java4
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig4
-rw-r--r--media/java/android/media/quality/AmbientBacklightSettings.java1
-rw-r--r--media/java/android/media/quality/IMediaQualityManager.aidl18
-rw-r--r--media/java/android/media/quality/IPictureProfileCallback.aidl9
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java176
-rw-r--r--media/java/android/media/quality/ParamCapability.java6
-rw-r--r--media/java/android/media/quality/PictureProfile.java47
-rw-r--r--media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java22
-rw-r--r--native/android/Android.bp2
-rw-r--r--native/android/dynamic_instrumentation_manager.cpp173
-rw-r--r--native/android/include_platform/android/dynamic_instrumentation_manager.h132
-rw-r--r--native/android/libandroid.map.txt9
-rw-r--r--packages/SettingsLib/Android.bp16
-rw-r--r--packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java15
-rw-r--r--packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java15
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java2
-rw-r--r--packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java2
-rw-r--r--packages/SettingsLib/IntroPreference/Android.bp1
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/Android.bp2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml13
-rw-r--r--packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt2
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java37
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java14
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java133
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/Shell/AndroidManifest.xml9
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java2
-rw-r--r--packages/SystemUI/README.md2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt233
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt40
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt555
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt85
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt55
-rw-r--r--packages/SystemUI/res/values/config.xml5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt119
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt314
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt131
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt4
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java30
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java16
-rw-r--r--ravenwood/runtime-helper-src/framework/android/util/Log_host.java4
-rw-r--r--ravenwood/texts/ravenwood-annotation-allowed-classes.txt5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java60
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java77
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java64
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java26
-rw-r--r--services/art-profile2
-rw-r--r--services/art-wear-profile4
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java3
-rw-r--r--services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java7
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java62
-rw-r--r--services/core/java/com/android/server/adaptiveauth/OWNERS1
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java5
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java149
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java7
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java23
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java69
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java25
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java19
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java58
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java10
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java8
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java6
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java26
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java35
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java60
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java66
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java24
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java207
-rw-r--r--services/core/java/com/android/server/net/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java37
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java28
-rw-r--r--services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java135
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java10
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java38
-rw-r--r--services/core/java/com/android/server/policy/WindowWakeUpPolicy.java37
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java15
-rw-r--r--services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java (renamed from services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java)10
-rw-r--r--services/core/java/com/android/server/security/adaptiveauthentication/OWNERS3
-rw-r--r--services/core/java/com/android/server/vibrator/ExternalVibrationSession.java16
-rw-r--r--services/core/java/com/android/server/vibrator/SingleVibrationSession.java14
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java493
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java17
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSession.java33
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java517
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java16
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java36
-rw-r--r--services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java7
-rw-r--r--services/core/java/com/android/server/wm/Transition.java40
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java51
-rw-r--r--services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp131
-rw-r--r--services/java/com/android/server/SystemServer.java14
-rw-r--r--services/proguard.flags2
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/Android.bp44
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml27
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING7
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java24
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java40
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java33
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java143
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java102
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt476
-rw-r--r--services/tests/mockingservicestests/Android.bp1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java384
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java170
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java292
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java88
-rw-r--r--services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java (renamed from services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java)19
-rw-r--r--services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java83
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java31
-rw-r--r--services/tests/vibrator/AndroidManifest.xml3
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java537
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java95
-rw-r--r--telecomm/java/android/telecom/Log.java19
-rw-r--r--telecomm/java/android/telecom/Logging/SessionManager.java34
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java54
-rw-r--r--telephony/java/android/telephony/satellite/EarfcnRange.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/EarfcnRange.java124
-rw-r--r--telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl12
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java122
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java14
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteInfo.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteInfo.java169
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java124
-rw-r--r--telephony/java/android/telephony/satellite/SatellitePosition.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/SatellitePosition.java114
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl12
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt4
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt48
-rw-r--r--tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java32
-rw-r--r--tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt2
418 files changed, 16258 insertions, 3914 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 497619ae0613..ad849002cca1 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -21,6 +21,7 @@ aconfig_declarations_group {
java_aconfig_libraries: [
// !!! KEEP THIS LIST ALPHABETICAL !!!
"aconfig_mediacodec_flags_java_lib",
+ "aconfig_settingslib_flags_java_lib",
"aconfig_trade_in_mode_flags_java_lib",
"android-sdk-flags-java",
"android.adaptiveauth.flags-aconfig-java",
@@ -1757,3 +1758,19 @@ cc_aconfig_library {
],
min_sdk_version: "apex_inherit",
}
+
+// Settings Lib
+aconfig_declarations {
+ name: "aconfig_settingslib_flags",
+ package: "com.android.settingslib.flags",
+ container: "system",
+ srcs: [
+ "packages/SettingsLib/aconfig/settingslib.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "aconfig_settingslib_flags_java_lib",
+ aconfig_declarations: "aconfig_settingslib_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 26d0d65f329c..48f0928f24d7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -220,7 +220,7 @@ java_library {
"android.hardware.contexthub-V1.0-java",
"android.hardware.contexthub-V1.1-java",
"android.hardware.contexthub-V1.2-java",
- "android.hardware.contexthub-V3-java",
+ "android.hardware.contexthub-V4-java",
"android.hardware.gnss-V1.0-java",
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
@@ -399,6 +399,7 @@ java_defaults {
"com.android.sysprop.foldlockbehavior",
"com.android.sysprop.view",
"framework-internal-utils",
+ "dynamic_instrumentation_manager_aidl-java",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
"mimemap",
diff --git a/api/Android.bp b/api/Android.bp
index 0ac85e28de1a..cdc5cd120956 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -127,27 +127,54 @@ combined_apis {
}),
}
+// Create a single file containing the latest released version of the whole
+// Android public API.
+java_genrule {
+ name: "android.api.merged.public.latest",
+ srcs: [
+ ":android.api.combined.public.latest",
+ ],
+ out: ["public-latest.txt"],
+ tools: ["metalava"],
+ cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android public API is compatible with the
+// previously released public API.
java_genrule {
name: "frameworks-base-api-current-compat",
srcs: [
- ":android.api.public.latest",
+ ":android.api.merged.public.latest",
":android-incompatibilities.api.public.latest",
":frameworks-base-api-current.txt",
],
out: ["updated-baseline.txt"],
tools: ["metalava"],
cmd: metalava_cmd +
- "--check-compatibility:api:released $(location :android.api.public.latest) " +
+ "--check-compatibility:api:released $(location :android.api.merged.public.latest) " +
"--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
"--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
"$(location :frameworks-base-api-current.txt)",
}
+// Create a single file containing the latest released version of the whole
+// Android system API.
+java_genrule {
+ name: "android.api.merged.system.latest",
+ srcs: [
+ ":android.api.combined.system.latest",
+ ],
+ out: ["system-latest.txt"],
+ tools: ["metalava"],
+ cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android system API is compatible with the
+// previously released system API.
java_genrule {
name: "frameworks-base-api-system-current-compat",
srcs: [
- ":android.api.public.latest",
- ":android.api.system.latest",
+ ":android.api.merged.system.latest",
":android-incompatibilities.api.system.latest",
":frameworks-base-api-current.txt",
":frameworks-base-api-system-current.txt",
@@ -155,20 +182,31 @@ java_genrule {
out: ["updated-baseline.txt"],
tools: ["metalava"],
cmd: metalava_cmd +
- "--check-compatibility:api:released $(location :android.api.public.latest) " +
- "--check-compatibility:api:released $(location :android.api.system.latest) " +
+ "--check-compatibility:api:released $(location :android.api.merged.system.latest) " +
"--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
"--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
"$(location :frameworks-base-api-current.txt) " +
"$(location :frameworks-base-api-system-current.txt)",
}
+// Create a single file containing the latest released version of the whole
+// Android module-lib API.
+java_genrule {
+ name: "android.api.merged.module-lib.latest",
+ srcs: [
+ ":android.api.combined.module-lib.latest",
+ ],
+ out: ["module-lib-latest.txt"],
+ tools: ["metalava"],
+ cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android module-lib API is compatible with the
+// previously released module-lib API.
java_genrule {
name: "frameworks-base-api-module-lib-current-compat",
srcs: [
- ":android.api.public.latest",
- ":android.api.system.latest",
- ":android.api.module-lib.latest",
+ ":android.api.merged.module-lib.latest",
":android-incompatibilities.api.module-lib.latest",
":frameworks-base-api-current.txt",
":frameworks-base-api-system-current.txt",
@@ -177,9 +215,7 @@ java_genrule {
out: ["updated-baseline.txt"],
tools: ["metalava"],
cmd: metalava_cmd +
- "--check-compatibility:api:released $(location :android.api.public.latest) " +
- "--check-compatibility:api:released $(location :android.api.system.latest) " +
- "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+ "--check-compatibility:api:released $(location :android.api.merged.module-lib.latest) " +
"--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
"--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
"$(location :frameworks-base-api-current.txt) " +
@@ -194,7 +230,7 @@ java_genrule {
cmd: "$(location merge_zips) $(out) $(in)",
srcs: [
":api-stubs-docs-non-updatable{.exportable}",
- ":all-modules-public-stubs-source",
+ ":all-modules-public-stubs-source-exportable",
],
visibility: ["//visibility:private"], // Used by make module in //development, mind
}
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e8fcf4b2b32d..1ebe0cdfabd7 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -129,7 +129,7 @@ droidstubs {
droidstubs {
name: "framework-doc-stubs",
defaults: ["android-non-updatable-doc-stubs-defaults"],
- srcs: [":all-modules-public-stubs-source"],
+ srcs: [":all-modules-public-stubs-source-exportable"],
api_levels_module: "api_versions_public",
aidl: {
include_dirs: [
diff --git a/api/api.go b/api/api.go
index f32bdc32f75d..5ca24de1b46a 100644
--- a/api/api.go
+++ b/api/api.go
@@ -429,8 +429,9 @@ func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContex
func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
props := fgProps{}
- props.Name = proptools.StringPtr("all-modules-public-stubs-source")
- props.Device_common_srcs = createSrcs(modules, "{.public.stubs.source}")
+ props.Name = proptools.StringPtr("all-modules-public-stubs-source-exportable")
+ transformConfigurableArray(modules, "", ".stubs.source")
+ props.Device_common_srcs = createSrcs(modules, "{.exportable}")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(android.FileGroupFactory, &props)
}
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 3c0e118bbfe7..57ae3548123b 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -17,6 +17,7 @@
#include "idmap2/ResourceContainer.h"
#include <memory>
+#include <mutex>
#include <string>
#include <utility>
#include <vector>
@@ -296,7 +297,7 @@ struct ResState {
} // namespace
struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
- static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+ static Result<std::unique_ptr<ApkResourceContainer>> FromPath(std::string path);
// inherited from TargetResourceContainer
Result<bool> DefinesOverlayable() const override;
@@ -320,6 +321,7 @@ struct ApkResourceContainer : public TargetResourceContainer, public OverlayReso
Result<const ResState*> GetState() const;
ZipAssetsProvider* GetZipAssets() const;
+ mutable std::mutex state_lock_;
mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
std::string path_;
};
@@ -330,16 +332,17 @@ ApkResourceContainer::ApkResourceContainer(std::unique_ptr<ZipAssetsProvider> zi
}
Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
- const std::string& path) {
+ std::string path) {
auto zip_assets = ZipAssetsProvider::Create(path, 0 /* flags */);
if (zip_assets == nullptr) {
return Error("failed to load zip assets");
}
return std::unique_ptr<ApkResourceContainer>(
- new ApkResourceContainer(std::move(zip_assets), path));
+ new ApkResourceContainer(std::move(zip_assets), std::move(path)));
}
Result<const ResState*> ApkResourceContainer::GetState() const {
+ std::lock_guard lock(state_lock_);
if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
return state;
}
@@ -355,6 +358,7 @@ Result<const ResState*> ApkResourceContainer::GetState() const {
}
ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+ std::lock_guard lock(state_lock_);
if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
return zip->get();
}
@@ -427,7 +431,7 @@ Result<std::string> ApkResourceContainer::GetResourceName(ResourceId id) const {
Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
std::string path) {
- auto result = ApkResourceContainer::FromPath(path);
+ auto result = ApkResourceContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
@@ -438,7 +442,7 @@ Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::From
std::string path) {
// Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
if (android::IsFabricatedOverlay(path)) {
- auto result = FabricatedOverlayContainer::FromPath(path);
+ auto result = FabricatedOverlayContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
@@ -446,7 +450,7 @@ Result<std::unique_ptr<OverlayResourceContainer>> OverlayResourceContainer::From
}
// Fallback to loading the container as an APK.
- auto result = ApkResourceContainer::FromPath(path);
+ auto result = ApkResourceContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index ead655426bf9..2f5bde9d37f2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -263,6 +263,7 @@ package android {
field public static final String READ_SMS = "android.permission.READ_SMS";
field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
+ field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String READ_SYSTEM_PREFERENCES = "android.permission.READ_SYSTEM_PREFERENCES";
field public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
field public static final String REBOOT = "android.permission.REBOOT";
field public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED";
@@ -313,6 +314,7 @@ package android {
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON";
+ field @FlaggedApi("android.app.enable_tv_implicit_enter_pip_restriction") public static final String TV_IMPLICIT_ENTER_PIP = "android.permission.TV_IMPLICIT_ENTER_PIP";
field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
@@ -334,6 +336,7 @@ package android {
field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS";
field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
+ field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES";
field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL";
}
@@ -475,6 +478,8 @@ package android {
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticModifiers = 16844110; // 0x101054e
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
+ field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherIcons;
+ field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherLabels;
field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
field public static final int alwaysRetainTaskState = 16843267; // 0x1010203
field @Deprecated public static final int amPmBackgroundColor = 16843941; // 0x10104a5
@@ -8787,8 +8792,31 @@ package android.app.admin {
package android.app.appfunctions {
+ @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionException extends java.lang.Exception implements android.os.Parcelable {
+ ctor public AppFunctionException(int, @Nullable String);
+ ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+ method public int describeContents();
+ method public int getErrorCategory();
+ method public int getErrorCode();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.AppFunctionException> CREATOR;
+ field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+ field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+ field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+ field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+ field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+ field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_DENIED = 1000; // 0x3e8
+ field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+ field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+ field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+ }
+
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -8800,7 +8828,7 @@ package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8822,30 +8850,14 @@ package android.app.appfunctions {
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method public int describeContents();
- method public int getErrorCategory();
- method @Nullable public String getErrorMessage();
method @NonNull public android.os.Bundle getExtras();
- method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- method public boolean isSuccess();
- method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
- field public static final int ERROR_CATEGORY_APP = 3; // 0x3
- field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
- field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
- field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
- field public static final String PROPERTY_RETURN_VALUE = "returnValue";
- field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
- field public static final int RESULT_CANCELLED = 2001; // 0x7d1
- field public static final int RESULT_DENIED = 1000; // 0x3e8
- field public static final int RESULT_DISABLED = 1002; // 0x3ea
- field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
- field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
- field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
+ field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
}
}
@@ -8868,6 +8880,7 @@ package android.app.assist {
method public void setWebUri(android.net.Uri);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR;
+ field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA";
}
public class AssistStructure implements android.os.Parcelable {
@@ -19770,6 +19783,8 @@ package android.hardware.camera2 {
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; // 0x0
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; // 0x1
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.zoom_method") public static final int CONTROL_ZOOM_METHOD_AUTO = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.zoom_method") public static final int CONTROL_ZOOM_METHOD_ZOOM_RATIO = 1; // 0x1
field public static final int DISTORTION_CORRECTION_MODE_FAST = 1; // 0x1
field public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
field public static final int DISTORTION_CORRECTION_MODE_OFF = 0; // 0x0
@@ -19777,6 +19792,9 @@ package android.hardware.camera2 {
field public static final int EDGE_MODE_HIGH_QUALITY = 2; // 0x2
field public static final int EDGE_MODE_OFF = 0; // 0x0
field public static final int EDGE_MODE_ZERO_SHUTTER_LAG = 3; // 0x3
+ field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; // 0x0
field public static final int FLASH_MODE_OFF = 0; // 0x0
field public static final int FLASH_MODE_SINGLE = 1; // 0x1
field public static final int FLASH_MODE_TORCH = 2; // 0x2
@@ -19974,6 +19992,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.zoom_method") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_ZOOM_METHOD;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
@@ -20072,10 +20091,12 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.zoom_method") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_ZOOM_METHOD;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE;
+ field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_NIGHT_MODE_INDICATOR;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE;
@@ -23973,6 +23994,7 @@ package android.media {
field public static final String KEY_MPEGH_COMPATIBLE_SETS = "mpegh-compatible-sets";
field public static final String KEY_MPEGH_PROFILE_LEVEL_INDICATION = "mpegh-profile-level-indication";
field public static final String KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT = "mpegh-reference-channel-layout";
+ field @FlaggedApi("android.media.codec.num_input_slots") public static final String KEY_NUM_SLOTS = "num-slots";
field public static final String KEY_OPERATING_RATE = "operating-rate";
field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
field public static final String KEY_PCM_ENCODING = "pcm-encoding";
@@ -40371,7 +40393,7 @@ package android.security.keystore {
method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean);
}
- @FlaggedApi("android.security.keystore_grant_api") public class KeyStoreManager {
+ @FlaggedApi("android.security.keystore_grant_api") public final class KeyStoreManager {
method @NonNull public java.util.List<java.security.cert.X509Certificate> getGrantedCertificateChainFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
method @NonNull public java.security.Key getGrantedKeyFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
method @NonNull public java.security.KeyPair getGrantedKeyPairFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
@@ -41999,6 +42021,193 @@ package android.service.restrictions {
}
+package android.service.settings.preferences {
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getPreferenceKey();
+ method @NonNull public String getScreenKey();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR;
+ }
+
+ public static final class GetValueRequest.Builder {
+ ctor public GetValueRequest.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.service.settings.preferences.GetValueRequest build();
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.service.settings.preferences.SettingsPreferenceMetadata getMetadata();
+ method public int getResultCode();
+ method @Nullable public android.service.settings.preferences.SettingsPreferenceValue getValue();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueResult> CREATOR;
+ field public static final int RESULT_DISALLOW = 4; // 0x4
+ field public static final int RESULT_INTERNAL_ERROR = 6; // 0x6
+ field public static final int RESULT_INVALID_REQUEST = 5; // 0x5
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_REQUIRE_APP_PERMISSION = 3; // 0x3
+ field public static final int RESULT_UNAVAILABLE = 2; // 0x2
+ field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+ }
+
+ public static final class GetValueResult.Builder {
+ ctor public GetValueResult.Builder(int);
+ method @NonNull public android.service.settings.preferences.GetValueResult build();
+ method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata);
+ method @NonNull public android.service.settings.preferences.GetValueResult.Builder setValue(@Nullable android.service.settings.preferences.SettingsPreferenceValue);
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR;
+ }
+
+ public static final class MetadataRequest.Builder {
+ ctor public MetadataRequest.Builder();
+ method @NonNull public android.service.settings.preferences.MetadataRequest build();
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata> getMetadataList();
+ method public int getResultCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataResult> CREATOR;
+ field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+ }
+
+ public static final class MetadataResult.Builder {
+ ctor public MetadataResult.Builder(int);
+ method @NonNull public android.service.settings.preferences.MetadataResult build();
+ method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>);
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getPreferenceKey();
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue getPreferenceValue();
+ method @NonNull public String getScreenKey();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR;
+ }
+
+ public static final class SetValueRequest.Builder {
+ ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue);
+ method @NonNull public android.service.settings.preferences.SetValueRequest build();
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getResultCode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueResult> CREATOR;
+ field public static final int RESULT_DISABLED = 2; // 0x2
+ field public static final int RESULT_DISALLOW = 7; // 0x7
+ field public static final int RESULT_INTERNAL_ERROR = 9; // 0x9
+ field public static final int RESULT_INVALID_REQUEST = 8; // 0x8
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_REQUIRE_APP_PERMISSION = 5; // 0x5
+ field public static final int RESULT_REQUIRE_USER_CONSENT = 6; // 0x6
+ field public static final int RESULT_RESTRICTED = 3; // 0x3
+ field public static final int RESULT_UNAVAILABLE = 4; // 0x4
+ field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+ }
+
+ public static final class SetValueResult.Builder {
+ ctor public SetValueResult.Builder(int);
+ method @NonNull public android.service.settings.preferences.SetValueResult build();
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getBreadcrumbs();
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getKey();
+ method @Nullable public android.app.PendingIntent getLaunchIntent();
+ method @NonNull public java.util.List<java.lang.String> getReadPermissions();
+ method @NonNull public String getScreenKey();
+ method @Nullable public String getSummary();
+ method @Nullable public String getTitle();
+ method @NonNull public java.util.List<java.lang.String> getWritePermissions();
+ method public int getWriteSensitivity();
+ method public boolean isAvailable();
+ method public boolean isEnabled();
+ method public boolean isRestricted();
+ method public boolean isWritable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR;
+ field public static final int INTENT_ONLY = 2; // 0x2
+ field public static final int NOT_SENSITIVE = 0; // 0x0
+ field public static final int SENSITIVE = 1; // 0x1
+ }
+
+ public static final class SettingsPreferenceMetadata.Builder {
+ ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build();
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.app.PendingIntent);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setReadPermissions(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setRestricted(boolean);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setSummary(@Nullable String);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setTitle(@Nullable String);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritable(boolean);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritePermissions(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWriteSensitivity(int);
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public abstract class SettingsPreferenceService extends android.app.Service {
+ ctor public SettingsPreferenceService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method public abstract void onGetAllPreferenceMetadata(@NonNull android.service.settings.preferences.MetadataRequest, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.MetadataResult,java.lang.Exception>);
+ method public abstract void onGetPreferenceValue(@NonNull android.service.settings.preferences.GetValueRequest, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.GetValueResult,java.lang.Exception>);
+ method public abstract void onSetPreferenceValue(@NonNull android.service.settings.preferences.SetValueRequest, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.SetValueResult,java.lang.Exception>);
+ field public static final String ACTION_PREFERENCE_SERVICE = "android.service.settings.preferences.action.PREFERENCE_SERVICE";
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public class SettingsPreferenceServiceClient implements java.lang.AutoCloseable {
+ ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String);
+ method public void close();
+ method public void getAllPreferenceMetadata(@NonNull android.service.settings.preferences.MetadataRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.MetadataResult,java.lang.Exception>);
+ method public void getPreferenceValue(@NonNull android.service.settings.preferences.GetValueRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.GetValueResult,java.lang.Exception>);
+ method public void setPreferenceValue(@NonNull android.service.settings.preferences.SetValueRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.SetValueResult,java.lang.Exception>);
+ method public void start();
+ method public void stop();
+ }
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceValue implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean getBooleanValue();
+ method public double getDoubleValue();
+ method public long getLongValue();
+ method @Nullable public String getStringValue();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceValue> CREATOR;
+ field public static final int TYPE_BOOLEAN = 0; // 0x0
+ field public static final int TYPE_DOUBLE = 2; // 0x2
+ field public static final int TYPE_LONG = 1; // 0x1
+ field public static final int TYPE_STRING = 3; // 0x3
+ }
+
+ public static final class SettingsPreferenceValue.Builder {
+ ctor public SettingsPreferenceValue.Builder(int);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build();
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setDoubleValue(double);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setLongValue(long);
+ method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setStringValue(@Nullable String);
+ }
+
+}
+
package android.service.textservice {
public abstract class SpellCheckerService extends android.app.Service {
@@ -44104,6 +44313,8 @@ package android.telephony {
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1; // 0x1
field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0
field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; // 0x1
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
@@ -44174,11 +44385,14 @@ package android.telephony {
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT = "carrier_roaming_ntn_connect_type_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY = "carrier_roaming_satellite_default_services_int_array";
field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = "carrier_supported_satellite_notification_hysteresis_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
@@ -44229,6 +44443,7 @@ package android.telephony {
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL = "disable_dun_apn_while_roaming_with_preset_apn_bool";
field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool";
@@ -44241,6 +44456,8 @@ package android.telephony {
field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT = "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL = "emergency_messaging_supported_bool";
field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool";
@@ -44287,6 +44504,7 @@ package android.telephony {
field public static final String KEY_MMS_MAX_IMAGE_HEIGHT_INT = "maxImageHeight";
field public static final String KEY_MMS_MAX_IMAGE_WIDTH_INT = "maxImageWidth";
field public static final String KEY_MMS_MAX_MESSAGE_SIZE_INT = "maxMessageSize";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT = "mms_max_ntn_payload_size_bytes_int";
field public static final String KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT = "maxMessageTextSize";
field public static final String KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL = "enableMMSDeliveryReports";
field public static final String KEY_MMS_MMS_ENABLED_BOOL = "enabledMMS";
@@ -44325,6 +44543,7 @@ package android.telephony {
field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long";
field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL = "override_wfc_roaming_mode_while_using_ntn_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
@@ -44343,6 +44562,7 @@ package android.telephony {
field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool";
field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool";
@@ -44354,13 +44574,18 @@ package android.telephony {
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING = "satellite_information_redirect_url_string";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = "satellite_nidd_apn_name_string";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_esos_inactivity_timeout_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_p2p_sms_inactivity_timeout_sec_int";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_screen_off_inactivity_timeout_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = "satellite_roaming_turn_off_session_for_emergency_call_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -44430,6 +44655,9 @@ package android.telephony {
field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ALL = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0; // 0x0
field public static final int SERVICE_CLASS_NONE = 0; // 0x0
field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
@@ -51799,6 +52027,7 @@ package android.view {
field public static final int KEYCODE_CHANNEL_DOWN = 167; // 0xa7
field public static final int KEYCODE_CHANNEL_UP = 166; // 0xa6
field public static final int KEYCODE_CLEAR = 28; // 0x1c
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_CLOSE = 321; // 0x141
field public static final int KEYCODE_COMMA = 55; // 0x37
field public static final int KEYCODE_CONTACTS = 207; // 0xcf
field public static final int KEYCODE_COPY = 278; // 0x116
@@ -51811,6 +52040,8 @@ package android.view {
field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e
field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f
field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DICTATE = 319; // 0x13f
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DO_NOT_DISTURB = 322; // 0x142
field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
@@ -51823,7 +52054,7 @@ package android.view {
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
- field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
+ field public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
field public static final int KEYCODE_ENDCALL = 6; // 0x6
field public static final int KEYCODE_ENTER = 66; // 0x42
field public static final int KEYCODE_ENVELOPE = 65; // 0x41
@@ -51835,7 +52066,19 @@ package android.view {
field public static final int KEYCODE_F10 = 140; // 0x8c
field public static final int KEYCODE_F11 = 141; // 0x8d
field public static final int KEYCODE_F12 = 142; // 0x8e
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F13 = 326; // 0x146
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F14 = 327; // 0x147
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F15 = 328; // 0x148
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F16 = 329; // 0x149
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F17 = 330; // 0x14a
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F18 = 331; // 0x14b
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F19 = 332; // 0x14c
field public static final int KEYCODE_F2 = 132; // 0x84
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F20 = 333; // 0x14d
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F21 = 334; // 0x14e
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F22 = 335; // 0x14f
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F23 = 336; // 0x150
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F24 = 337; // 0x151
field public static final int KEYCODE_F3 = 133; // 0x85
field public static final int KEYCODE_F4 = 134; // 0x86
field public static final int KEYCODE_F5 = 135; // 0x87
@@ -51850,6 +52093,7 @@ package android.view {
field public static final int KEYCODE_FOCUS = 80; // 0x50
field public static final int KEYCODE_FORWARD = 125; // 0x7d
field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_FULLSCREEN = 325; // 0x145
field public static final int KEYCODE_FUNCTION = 119; // 0x77
field public static final int KEYCODE_G = 35; // 0x23
field public static final int KEYCODE_GRAVE = 68; // 0x44
@@ -51873,6 +52117,7 @@ package android.view {
field public static final int KEYCODE_LANGUAGE_SWITCH = 204; // 0xcc
field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5
field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_LOCK = 324; // 0x144
field public static final int KEYCODE_M = 41; // 0x29
field public static final int KEYCODE_MACRO_1 = 313; // 0x139
field public static final int KEYCODE_MACRO_2 = 314; // 0x13a
@@ -51910,6 +52155,7 @@ package android.view {
field public static final int KEYCODE_NAVIGATE_NEXT = 261; // 0x105
field public static final int KEYCODE_NAVIGATE_OUT = 263; // 0x107
field public static final int KEYCODE_NAVIGATE_PREVIOUS = 260; // 0x104
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_NEW = 320; // 0x140
field public static final int KEYCODE_NOTIFICATION = 83; // 0x53
field public static final int KEYCODE_NUM = 78; // 0x4e
field public static final int KEYCODE_NUMPAD_0 = 144; // 0x90
@@ -51944,6 +52190,7 @@ package android.view {
field public static final int KEYCODE_PLUS = 81; // 0x51
field public static final int KEYCODE_POUND = 18; // 0x12
field public static final int KEYCODE_POWER = 26; // 0x1a
+ field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_PRINT = 323; // 0x143
field public static final int KEYCODE_PROFILE_SWITCH = 288; // 0x120
field public static final int KEYCODE_PROG_BLUE = 186; // 0xba
field public static final int KEYCODE_PROG_GREEN = 184; // 0xb8
@@ -51956,7 +52203,7 @@ package android.view {
field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
field public static final int KEYCODE_RO = 217; // 0xd9
field public static final int KEYCODE_S = 47; // 0x2f
- field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
+ field public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74
field public static final int KEYCODE_SEARCH = 84; // 0x54
field public static final int KEYCODE_SEMICOLON = 74; // 0x4a
@@ -55410,7 +55657,7 @@ package android.view.accessibility {
method public android.os.Bundle getExtras();
method public CharSequence getHintText();
method public int getInputType();
- method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
@@ -55509,8 +55756,8 @@ package android.view.accessibility {
method public void setHintText(CharSequence);
method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
- method public void setLabelFor(android.view.View);
- method public void setLabelFor(android.view.View, int);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View, int);
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View);
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View, int);
method public void setLiveRegion(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2a01ca082832..8954f8e70157 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@ package android {
field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+ field @FlaggedApi("com.android.art.flags.executable_method_file_offsets") public static final String DYNAMIC_INSTRUMENTATION = "android.permission.DYNAMIC_INSTRUMENTATION";
field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
@@ -389,6 +390,7 @@ package android {
field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
+ field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String START_VIBRATION_SESSIONS = "android.permission.START_VIBRATION_SESSIONS";
field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
@@ -705,6 +707,7 @@ package android.app {
field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+ field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
@@ -11660,8 +11663,11 @@ package android.os {
public abstract class Vibrator {
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+ method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorEffectsSupported();
+ method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorSessionsSupported();
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+ method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @RequiresPermission(allOf={android.Manifest.permission.VIBRATE, android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, android.Manifest.permission.START_VIBRATION_SESSIONS}) public void startVendorSession(@NonNull android.os.VibrationAttributes, @Nullable String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.vibrator.VendorVibrationSession.Callback);
}
public static interface Vibrator.OnVibratorStateChangedListener {
@@ -11813,6 +11819,28 @@ package android.os.storage {
}
+package android.os.vibrator {
+
+ @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public final class VendorVibrationSession implements java.lang.AutoCloseable {
+ method public void cancel();
+ method public void close();
+ method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @Nullable String);
+ field public static final int STATUS_CANCELED = 4; // 0x4
+ field public static final int STATUS_IGNORED = 2; // 0x2
+ field public static final int STATUS_SUCCESS = 1; // 0x1
+ field public static final int STATUS_UNKNOWN = 0; // 0x0
+ field public static final int STATUS_UNKNOWN_ERROR = 5; // 0x5
+ field public static final int STATUS_UNSUPPORTED = 3; // 0x3
+ }
+
+ public static interface VendorVibrationSession.Callback {
+ method public void onFinished(int);
+ method public void onFinishing();
+ method public void onStarted(@NonNull android.os.vibrator.VendorVibrationSession);
+ }
+
+}
+
package android.os.vibrator.persistence {
@FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
@@ -18305,7 +18333,12 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; // 0x7
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; // 0x5
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_SMS = 6; // 0x6
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
@@ -18325,6 +18358,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
@@ -18338,6 +18372,8 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9; // 0x9
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8; // 0x8
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
@@ -18345,11 +18381,16 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28; // 0x1c
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27; // 0x1b
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; // 0x1d
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; // 0x19
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; // 0x1a
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 117351943587..3ca55b932e19 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3247,6 +3247,14 @@ package android.service.quicksettings {
}
+package android.service.settings.preferences {
+
+ @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public class SettingsPreferenceServiceClient implements java.lang.AutoCloseable {
+ ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String, boolean, @Nullable android.content.ServiceConnection);
+ }
+
+}
+
package android.service.voice {
public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
@@ -3729,7 +3737,7 @@ package android.view {
method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
- field public static final int LAST_KEYCODE = 318; // 0x13e
+ field public static final int LAST_KEYCODE = 337; // 0x151
}
public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 71623c566501..cf5ebbaa37b4 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -28,6 +28,7 @@ filegroup {
exclude_srcs: [
"android/os/*MessageQueue/**/*.java",
"android/ranging/**/*.java",
+ ":dynamic_instrumentation_manager_aidl_sources",
],
visibility: ["//frameworks/base"],
}
@@ -120,6 +121,17 @@ filegroup {
}
filegroup {
+ name: "dynamic_instrumentation_manager_aidl_sources",
+ srcs: ["android/os/instrumentation/*.aidl"],
+}
+
+aidl_interface {
+ name: "dynamic_instrumentation_manager_aidl",
+ srcs: [":dynamic_instrumentation_manager_aidl_sources"],
+ unstable: true,
+}
+
+filegroup {
name: "framework-internal-display-sources",
srcs: ["com/android/internal/display/BrightnessSynchronizer.java"],
visibility: ["//frameworks/base/services/tests/mockingservicestests"],
@@ -685,16 +697,31 @@ gen_readonly_feature_apis = select(release_flag("RELEASE_USE_SYSTEM_FEATURE_BUIL
// Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in
// details about fixed system features defined by build flags. When disabled,
// the APIs are simply passthrough stubs with no meaningful side effects.
+// TODO(b/203143243): Implement the `--feature=` aggregation directly with a native soong module.
genrule {
name: "systemfeatures-gen-srcs",
cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " +
// --readonly=false (default) makes the codegen an effective no-op passthrough API.
" --readonly=" + gen_readonly_feature_apis +
- // For now, only export "android.hardware.type.*" system features APIs.
- // TODO(b/203143243): Use an intermediate soong var that aggregates all declared
- // RELEASE_SYSTEM_FEATURE_* declarations into a single arg.
- " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" +
- " > $(out)",
+ " --feature=AUTOMOTIVE:" + select(release_flag("RELEASE_SYSTEM_FEATURE_AUTOMOTIVE"), {
+ any @ value: value,
+ default: "",
+ }) + " --feature=EMBEDDED:" + select(release_flag("RELEASE_SYSTEM_FEATURE_EMBEDDED"), {
+ any @ value: value,
+ default: "",
+ }) + " --feature=LEANBACK:" + select(release_flag("RELEASE_SYSTEM_FEATURE_LEANBACK"), {
+ any @ value: value,
+ default: "",
+ }) + " --feature=PC:" + select(release_flag("RELEASE_SYSTEM_FEATURE_PC"), {
+ any @ value: value,
+ default: "",
+ }) + " --feature=TELEVISION:" + select(release_flag("RELEASE_SYSTEM_FEATURE_TELEVISION"), {
+ any @ value: value,
+ default: "",
+ }) + " --feature=WATCH:" + select(release_flag("RELEASE_SYSTEM_FEATURE_WATCH"), {
+ any @ value: value,
+ default: "",
+ }) + " > $(out)",
out: [
"RoSystemFeatures.java",
],
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
index 0218a7835586..bc8efa92c16f 100644
--- a/core/java/android/adaptiveauth/OWNERS
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -1 +1 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS \ No newline at end of file
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS \ No newline at end of file
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7a1c759a3ec4..3fccc17e1bf1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@ import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import static java.lang.Character.MIN_VALUE;
+import android.Manifest;
import android.annotation.AnimRes;
import android.annotation.CallSuper;
import android.annotation.CallbackExecutor;
@@ -3193,6 +3194,16 @@ public class Activity extends ContextThemeWrapper
return ActivityTaskManager.getMaxNumPictureInPictureActions(this);
}
+ private boolean isImplicitEnterPipProhibited() {
+ PackageManager pm = getPackageManager();
+ if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+ return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP,
+ getPackageName()) == PackageManager.PERMISSION_DENIED;
+ }
+ return false;
+ }
+
/**
* @return Whether this device supports picture-in-picture.
*/
@@ -9192,6 +9203,8 @@ public class Activity extends ContextThemeWrapper
}
dispatchActivityPreResumed();
+ mCanEnterPictureInPicture = true;
+
mFragments.execPendingActions();
mLastNonConfigurationInstances = null;
@@ -9243,6 +9256,11 @@ public class Activity extends ContextThemeWrapper
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:"
+ mComponent.getClassName());
}
+
+ if (isImplicitEnterPipProhibited()) {
+ mCanEnterPictureInPicture = false;
+ }
+
dispatchActivityPrePaused();
mDoReportFullyDrawn = false;
mFragments.dispatchPause();
@@ -9265,6 +9283,10 @@ public class Activity extends ContextThemeWrapper
final void performUserLeaving() {
onUserInteraction();
+
+ if (isImplicitEnterPipProhibited()) {
+ mCanEnterPictureInPicture = false;
+ }
onUserLeaveHint();
}
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 68794588afc5..009cd7249dcd 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -101,7 +101,6 @@ public class AppCompatTaskInfo implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
FLAG_UNDEFINED,
- FLAG_BASE,
FLAG_LETTERBOX_EDU_ENABLED,
FLAG_ELIGIBLE_FOR_LETTERBOX_EDU,
FLAG_LETTERBOXED,
@@ -115,6 +114,10 @@ public class AppCompatTaskInfo implements Parcelable {
})
public @interface TopActivityFlag {}
+ /**
+ * A combination of {@link TopActivityFlag}s that have been enabled through
+ * {@link #setTopActivityFlag}.
+ */
@TopActivityFlag
private int mTopActivityFlags;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 38c8583dd024..fd70f4fc3f25 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1621,9 +1621,12 @@ public class AppOpsManager {
*/
public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING;
+ /** @hide Access to read oxygen saturation. */
+ public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 152;
+ public static final int _NUM_OP = 153;
/**
* All app ops represented as strings.
@@ -1779,6 +1782,7 @@ public class AppOpsManager {
OPSTR_READ_HEART_RATE,
OPSTR_READ_SKIN_TEMPERATURE,
OPSTR_RANGING,
+ OPSTR_READ_OXYGEN_SATURATION,
})
public @interface AppOpString {}
@@ -2521,6 +2525,11 @@ public class AppOpsManager {
@FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
+ /** @hide Access to read oxygen saturation. */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED)
+ public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
+
/** @hide Access to read skin temperature. */
@SystemApi
@FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
@@ -2608,6 +2617,7 @@ public class AppOpsManager {
// Health
Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
+ Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
};
/**
@@ -3129,6 +3139,11 @@ public class AppOpsManager {
.setPermission(Flags.rangingPermissionEnabled()?
Manifest.permission.RANGING : null)
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION,
+ "READ_OXYGEN_SATURATION").setPermission(
+ Flags.platformOxygenSaturationEnabled()
+ ? HealthPermissions.READ_OXYGEN_SATURATION : null)
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index f432a22b14ac..1dc774285a32 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -32,7 +32,10 @@ import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -92,9 +95,6 @@ public class PropertyInvalidatedCache<Query, Result> {
* caching on behalf of other processes.
*/
public boolean shouldBypassCache(@NonNull Q query) {
- if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) {
- return Binder.getCallingUid() != Process.myUid();
- }
return false;
}
};
@@ -392,8 +392,213 @@ public class PropertyInvalidatedCache<Query, Result> {
}
}
+ /**
+ * An array of hash maps, indexed by calling UID. The class behaves a bit like a hash map
+ * except that it uses the calling UID internally.
+ */
+ private class CacheMap<Query, Result> {
+
+ // Create a new map for a UID, using the parent's configuration for max size.
+ private LinkedHashMap<Query, Result> createMap() {
+ return new LinkedHashMap<Query, Result>(
+ 2 /* start small */,
+ 0.75f /* default load factor */,
+ true /* LRU access order */) {
+ @GuardedBy("mLock")
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ final int size = size();
+ if (size > mHighWaterMark) {
+ mHighWaterMark = size;
+ }
+ if (size > mMaxEntries) {
+ mMissOverflow++;
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ // An array of maps, indexed by UID.
+ private final SparseArray<LinkedHashMap<Query, Result>> mCache = new SparseArray<>();
+
+ // If true, isolate the hash entries by calling UID. If this is false, allow the cache
+ // entries to be combined in a single hash map.
+ private final boolean mIsolated;
+
+ // Collect statistics.
+ private final boolean mStatistics;
+
+ // An array of booleans to indicate if a UID has been involved in a map access. A value
+ // exists for every UID that was ever involved during cache access. This is updated only
+ // if statistics are being collected.
+ private final SparseBooleanArray mUidSeen;
+
+ // A hash map that ignores the UID. This is used in look-aside fashion just for hit/miss
+ // statistics. This is updated only if statistics are being collected.
+ private final ArraySet<Query> mShadowCache;
+
+ // Shadow statistics. Only hits and misses need to be recorded. These are updated only
+ // if statistics are being collected. The "SelfHits" records hits when the UID is the
+ // process uid.
+ private int mShadowHits;
+ private int mShadowMisses;
+ private int mShadowSelfHits;
+
+ // The process UID.
+ private final int mSelfUid;
+
+ // True in test mode. In test mode, the cache uses Binder.getWorkSource() as the UID.
+ private final boolean mTestMode;
+
+ /**
+ * Create a CacheMap. UID isolation is enabled if the input parameter is true and if the
+ * isolation feature is enabled.
+ */
+ CacheMap(boolean isolate, boolean testMode) {
+ mIsolated = Flags.picIsolateCacheByUid() && isolate;
+ mStatistics = Flags.picIsolatedCacheStatistics() && mIsolated;
+ if (mStatistics) {
+ mUidSeen = new SparseBooleanArray();
+ mShadowCache = new ArraySet<>();
+ } else {
+ mUidSeen = null;
+ mShadowCache = null;
+ }
+ mSelfUid = Process.myUid();
+ mTestMode = testMode;
+ }
+
+ // Return the UID for this cache invocation. If uid isolation is disabled, the value of 0
+ // is returned, which effectively places all entries in a single hash map.
+ private int callerUid() {
+ if (!mIsolated) {
+ return 0;
+ } else if (mTestMode) {
+ return Binder.getCallingWorkSourceUid();
+ } else {
+ return Binder.getCallingUid();
+ }
+ }
+
+ /**
+ * Lookup an entry in the cache.
+ */
+ Result get(Query query) {
+ final int uid = callerUid();
+
+ // Shadow statistics
+ if (mStatistics) {
+ if (mShadowCache.contains(query)) {
+ mShadowHits++;
+ if (uid == mSelfUid) {
+ mShadowSelfHits++;
+ }
+ } else {
+ mShadowMisses++;
+ }
+ }
+
+ var map = mCache.get(uid);
+ if (map != null) {
+ return map.get(query);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Remove an entry from the cache.
+ */
+ void remove(Query query) {
+ final int uid = callerUid();
+ if (mStatistics) {
+ mShadowCache.remove(query);
+ }
+
+ var map = mCache.get(uid);
+ if (map != null) {
+ map.remove(query);
+ }
+ }
+
+ /**
+ * Record an entry in the cache.
+ */
+ void put(Query query, Result result) {
+ final int uid = callerUid();
+ if (mStatistics) {
+ mShadowCache.add(query);
+ mUidSeen.put(uid, true);
+ }
+
+ var map = mCache.get(uid);
+ if (map == null) {
+ map = createMap();
+ mCache.put(uid, map);
+ }
+ map.put(query, result);
+ }
+
+ /**
+ * Return the number of entries in the cache.
+ */
+ int size() {
+ int total = 0;
+ for (int i = 0; i < mCache.size(); i++) {
+ var map = mCache.valueAt(i);
+ total += map.size();
+ }
+ return total;
+ }
+
+ /**
+ * Clear the entries in the cache. Update the shadow statistics.
+ */
+ void clear() {
+ if (mStatistics) {
+ mShadowCache.clear();
+ }
+
+ mCache.clear();
+ }
+
+ // Dump basic statistics, if any are collected. Do nothing if statistics are not enabled.
+ void dump(PrintWriter pw) {
+ if (mStatistics) {
+ pw.println(formatSimple(" ShadowHits: %d, ShadowMisses: %d, ShadowSize: %d",
+ mShadowHits, mShadowMisses, mShadowCache.size()));
+ pw.println(formatSimple(" ShadowUids: %d, SelfUid: %d",
+ mUidSeen.size(), mShadowSelfHits));
+ }
+ }
+
+ // Dump detailed statistics
+ void dumpDetailed(PrintWriter pw) {
+ for (int i = 0; i < mCache.size(); i++) {
+ int uid = mCache.keyAt(i);
+ var map = mCache.valueAt(i);
+
+ Set<Map.Entry<Query, Result>> cacheEntries = map.entrySet();
+ if (cacheEntries.size() == 0) {
+ break;
+ }
+
+ pw.println(" Contents:");
+ pw.println(formatSimple(" Uid: %d\n", uid));
+ for (Map.Entry<Query, Result> entry : cacheEntries) {
+ String key = Objects.toString(entry.getKey());
+ String value = Objects.toString(entry.getValue());
+
+ pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value));
+ }
+ }
+ }
+ }
+
@GuardedBy("mLock")
- private final LinkedHashMap<Query, Result> mCache;
+ private final CacheMap<Query, Result> mCache;
/**
* The nonce handler for this cache.
@@ -895,7 +1100,8 @@ public class PropertyInvalidatedCache<Query, Result> {
* is allowed to be null in the record constructor to facility reuse of Args instances.
* @hide
*/
- public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+ public static record Args(@NonNull String mModule, @Nullable String mApi,
+ int mMaxEntries, boolean mIsolateUids, boolean mTestMode) {
// Validation: the module must be one of the known module strings and the maxEntries must
// be positive.
@@ -909,15 +1115,28 @@ public class PropertyInvalidatedCache<Query, Result> {
// which is not legal, but there is no reasonable default. Clients must call the api
// method to set the field properly.
public Args(@NonNull String module) {
- this(module, /* api */ null, /* maxEntries */ 32);
+ this(module,
+ null, // api
+ 32, // maxEntries
+ true, // isolateUids
+ false // testMode
+ );
}
public Args api(@NonNull String api) {
- return new Args(mModule, api, mMaxEntries);
+ return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode);
}
public Args maxEntries(int val) {
- return new Args(mModule, mApi, val);
+ return new Args(mModule, mApi, val, mIsolateUids, mTestMode);
+ }
+
+ public Args isolateUids(boolean val) {
+ return new Args(mModule, mApi, mMaxEntries, val, mTestMode);
+ }
+
+ public Args testMode(boolean val) {
+ return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val);
}
}
@@ -936,7 +1155,7 @@ public class PropertyInvalidatedCache<Query, Result> {
mCacheName = cacheName;
mNonce = getNonceHandler(mPropertyName);
mMaxEntries = args.mMaxEntries;
- mCache = createMap();
+ mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode);
mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
registerCache();
}
@@ -1006,28 +1225,6 @@ public class PropertyInvalidatedCache<Query, Result> {
this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
- // Create a map. This should be called only from the constructor.
- private LinkedHashMap<Query, Result> createMap() {
- return new LinkedHashMap<Query, Result>(
- 2 /* start small */,
- 0.75f /* default load factor */,
- true /* LRU access order */) {
- @GuardedBy("mLock")
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- final int size = size();
- if (size > mHighWaterMark) {
- mHighWaterMark = size;
- }
- if (size > mMaxEntries) {
- mMissOverflow++;
- return true;
- }
- return false;
- }
- };
- }
-
/**
* Register the map in the global list. If the cache is disabled globally, disable it
* now. This method is only ever called from the constructor, which means no other thread has
@@ -1778,8 +1975,8 @@ public class PropertyInvalidatedCache<Query, Result> {
pw.println(formatSimple(" Cache Name: %s", cacheName()));
pw.println(formatSimple(" Property: %s", mPropertyName));
pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
- mHits, mMisses, getSkipsLocked(), mClears));
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
+ mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
// Print all the skip reasons.
pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1794,25 +1991,16 @@ public class PropertyInvalidatedCache<Query, Result> {
pw.println(formatSimple(
" Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+ mCache.dump(pw);
pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
- // No specific cache was requested. This is the default, and no details
- // should be dumped.
- if (!detailed) {
- return;
- }
- Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
- if (cacheEntries.size() == 0) {
- return;
+ // Dump the contents of the cache.
+ if (detailed) {
+ mCache.dumpDetailed(pw);
}
- pw.println(" Contents:");
- for (Map.Entry<Query, Result> entry : cacheEntries) {
- String key = Objects.toString(entry.getKey());
- String value = Objects.toString(entry.getValue());
-
- pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value));
- }
+ // Separator between caches.
+ pw.println("");
}
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 04a9d13420ee..be24bfa41e10 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -359,3 +359,18 @@ flag {
description: "Enables coexistence support for Setting MTE policy."
bug: "376213673"
}
+
+flag {
+ name: "enable_supervision_service_sync"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
+ bug: "376213673"
+}
+
+flag {
+ name: "split_create_managed_profile_enabled"
+ namespace: "enterprise"
+ description: "Split up existing create and provision managed profile API."
+ bug: "375382324"
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionException.aidl b/core/java/android/app/appfunctions/AppFunctionException.aidl
new file mode 100644
index 000000000000..7d432243b1b2
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.AppFunctionException;
+
+parcelable AppFunctionException; \ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
new file mode 100644
index 000000000000..d33b5055f9cc
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Represents an app function related errors. */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class AppFunctionException extends Exception implements Parcelable {
+ /**
+ * The caller does not have the permission to execute an app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DENIED = 1000;
+
+ /**
+ * The caller supplied invalid arguments to the execution request.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+ /**
+ * The caller tried to execute a disabled app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DISABLED = 1002;
+
+ /**
+ * The caller tried to execute a function that does not exist.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+ /**
+ * An internal unexpected error coming from the system.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_SYSTEM_ERROR = 2000;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_CANCELLED = 2001;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+ */
+ public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+ /**
+ * The error category is unknown.
+ *
+ * <p>This is the default value for {@link #getErrorCategory}.
+ */
+ public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * The error is caused by the app requesting a function execution.
+ *
+ * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+ * invalid function ID.
+ *
+ * <p>Errors in the category fall in the range 1000-1999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+ /**
+ * The error is caused by an issue in the system.
+ *
+ * <p>For example, the AppFunctionService implementation is not found by the system.
+ *
+ * <p>Errors in the category fall in the range 2000-2999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+ /**
+ * The error is caused by the app providing the function.
+ *
+ * <p>For example, the app crashed when the system is executing the request.
+ *
+ * <p>Errors in the category fall in the range 3000-3999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_APP = 3;
+
+ private final int mErrorCode;
+ @Nullable private final String mErrorMessage;
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public AppFunctionException(@ErrorCode int errorCode, @Nullable String errorMessage) {
+ this(errorCode, errorMessage, Bundle.EMPTY);
+ }
+
+ /**
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param extras The extras associated with this error.
+ */
+ public AppFunctionException(
+ @ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mExtras = Objects.requireNonNull(extras);
+ }
+
+ private AppFunctionException(@NonNull Parcel in) {
+ mErrorCode = in.readInt();
+ mErrorMessage = in.readString8();
+ mExtras = Objects.requireNonNull(in.readBundle(getClass().getClassLoader()));
+ }
+
+ /** Returns one of the {@code ERROR} constants. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Returns the error category.
+ *
+ * <p>This method categorizes errors based on their underlying cause, allowing developers to
+ * implement targeted error handling and provide more informative error messages to users. It
+ * maps ranges of error codes to specific error categories.
+ *
+ * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+ * any error category.
+ *
+ * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+ * error code ranges.
+ */
+ @ErrorCategory
+ public int getErrorCategory() {
+ if (mErrorCode >= 1000 && mErrorCode < 2000) {
+ return ERROR_CATEGORY_REQUEST_ERROR;
+ }
+ if (mErrorCode >= 2000 && mErrorCode < 3000) {
+ return ERROR_CATEGORY_SYSTEM;
+ }
+ if (mErrorCode >= 3000 && mErrorCode < 4000) {
+ return ERROR_CATEGORY_APP;
+ }
+ return ERROR_CATEGORY_UNKNOWN;
+ }
+
+ /** Returns any extras associated with this error. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mErrorCode);
+ dest.writeString8(mErrorMessage);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Error codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_DENIED,
+ ERROR_APP_UNKNOWN_ERROR,
+ ERROR_FUNCTION_NOT_FOUND,
+ ERROR_SYSTEM_ERROR,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_DISABLED,
+ ERROR_CANCELLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /**
+ * Error categories.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_CATEGORY_"},
+ value = {
+ ERROR_CATEGORY_UNKNOWN,
+ ERROR_CATEGORY_REQUEST_ERROR,
+ ERROR_CATEGORY_APP,
+ ERROR_CATEGORY_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCategory {}
+
+ @NonNull
+ public static final Creator<AppFunctionException> CREATOR =
+ new Creator<>() {
+ @Override
+ public AppFunctionException createFromParcel(Parcel in) {
+ return new AppFunctionException(in);
+ }
+
+ @Override
+ public AppFunctionException[] newArray(int size) {
+ return new AppFunctionException[size];
+ }
+ };
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 5ddb590add4c..ed088fed41c2 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -16,7 +16,7 @@
package android.app.appfunctions;
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
+import static android.app.appfunctions.AppFunctionException.ERROR_SYSTEM_ERROR;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.Manifest;
@@ -39,7 +39,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Provides access to app functions.
@@ -147,16 +146,16 @@ public final class AppFunctionManager {
* @param request the request to execute the app function
* @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
- * @param callback the callback to receive the function execution result.
+ * @param callback the callback to receive the function execution result or error.
* <p>If the calling app does not own the app function or does not have {@code
* android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
- * ExecuteAppFunctionResponse.RESULT_DENIED}.
+ * AppFunctionException.ERROR_DENIED}.
* <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
* function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
- * result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED}
+ * result will contain {@code AppFunctionException.ERROR_DENIED}
* <p>If the function requested for execution is disabled, then the execution result will
- * contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED}
+ * contain {@code AppFunctionException.ERROR_DISABLED}
* <p>If the cancellation signal is issued, the operation is cancelled and no response is
* returned to the caller.
*/
@@ -171,7 +170,9 @@ public final class AppFunctionManager {
@NonNull ExecuteAppFunctionRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -186,20 +187,25 @@ public final class AppFunctionManager {
aidlRequest,
new IExecuteAppFunctionCallback.Stub() {
@Override
- public void onResult(ExecuteAppFunctionResponse result) {
+ public void onSuccess(ExecuteAppFunctionResponse result) {
try {
- executor.execute(() -> callback.accept(result));
+ executor.execute(() -> callback.onResult(result));
} catch (RuntimeException e) {
// Ideally shouldn't happen since errors are wrapped into
- // the
- // response, but we catch it here for additional safety.
- callback.accept(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(e),
- e.getMessage(),
- /* extras= */ null));
+ // the response, but we catch it here for additional safety.
+ executor.execute(
+ () ->
+ callback.onError(
+ new AppFunctionException(
+ ERROR_SYSTEM_ERROR,
+ e.getMessage())));
}
}
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ executor.execute(() -> callback.onError(exception));
+ }
});
if (cancellationTransport != null) {
cancellationSignal.setRemote(cancellationTransport);
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 06d95f5270c3..3ddda228d145 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -213,9 +213,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
setEnabled(original.getEnabled());
}
- /**
- * Sets an indicator specifying the function enabled state.
- */
+ /** Sets an indicator specifying the function enabled state. */
@NonNull
public Builder setEnabled(@EnabledState int enabledState) {
if (enabledState != APP_FUNCTION_STATE_DEFAULT
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 63d187aa11ef..85b6ab2b4e61 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -17,7 +17,6 @@
package android.app.appfunctions;
import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -32,10 +31,8 @@ import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.function.Consumer;
/**
* Abstract base class to provide app functions to the system.
@@ -80,7 +77,9 @@ public abstract class AppFunctionService extends Service {
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
}
/** @hide */
@@ -105,13 +104,22 @@ public abstract class AppFunctionService extends Service {
request,
callingPackage,
buildCancellationSignal(cancellationCallback),
- safeCallback::onResult);
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ safeCallback.onResult(result);
+ }
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ safeCallback.onError(exception);
+ }
+ });
} catch (Exception ex) {
// Apps should handle exceptions. But if they don't, report the error on
// behalf of them.
- safeCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(ex), ex.getMessage(), /* extras= */ null));
+ safeCallback.onError(
+ new AppFunctionException(toErrorCode(ex), ex.getMessage()));
}
}
};
@@ -164,12 +172,26 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
+ * @param callback A callback to report back the result or error.
*/
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
+
+ /**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ private static @AppFunctionException.ErrorCode int toErrorCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return AppFunctionException.ERROR_INVALID_ARGUMENT;
+ }
+ return AppFunctionException.ERROR_APP_UNKNOWN_ERROR;
+ }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
index a23f842e6eeb..1869d22ea080 100644
--- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -38,7 +38,7 @@ public class AppFunctionStaticMetadataHelper {
public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
- "restrictCallersWithExecuteAppFunctions";
+ "restrictCallersWithExecuteAppFunctions";
public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
public static final String PROPERTY_FUNCTION_ID = "functionId";
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 41bb62270e9f..1557815a8468 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -111,8 +111,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable {
* Returns the function parameters. The key is the parameter name, and the value is the
* parameter value.
*
- * <p>The bundle may have missing parameters. Developers are advised to implement defensive
- * handling measures.
+ * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+ * implement defensive handling measures.
*
* @see AppFunctionManager on how to determine the expected parameters.
*/
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index cdf02e6f5a09..acad43b782e5 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -19,16 +19,12 @@ package android.app.appfunctions;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.appsearch.GenericDocument;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/** The response to an app function execution. */
@@ -45,10 +41,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
Bundle extras =
Objects.requireNonNull(
parcel.readBundle(Bundle.class.getClassLoader()));
- int resultCode = parcel.readInt();
- String errorMessage = parcel.readString8();
- return new ExecuteAppFunctionResponse(
- resultWrapper, extras, resultCode, errorMessage);
+ return new ExecuteAppFunctionResponse(resultWrapper.getValue(), extras);
}
@Override
@@ -71,113 +64,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
*
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
- public static final String PROPERTY_RETURN_VALUE = "returnValue";
-
- /**
- * The call was successful.
- *
- * <p>This result code does not belong in an error category.
- */
- public static final int RESULT_OK = 0;
-
- /**
- * The caller does not have the permission to execute an app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DENIED = 1000;
-
- /**
- * The caller supplied invalid arguments to the execution request.
- *
- * <p>This error may be considered similar to {@link IllegalArgumentException}.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_INVALID_ARGUMENT = 1001;
-
- /**
- * The caller tried to execute a disabled app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DISABLED = 1002;
-
- /**
- * The caller tried to execute a function that does not exist.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
- /**
- * An internal unexpected error coming from the system.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_SYSTEM_ERROR = 2000;
-
- /**
- * The operation was cancelled. Use this error code to report that a cancellation is done after
- * receiving a cancellation signal.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_CANCELLED = 2001;
-
- /**
- * An unknown error occurred while processing the call in the AppFunctionService.
- *
- * <p>This error is thrown when the service is connected in the remote application but an
- * unexpected error is thrown from the bound application.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
- */
- public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
- /**
- * The error category is unknown.
- *
- * <p>This is the default value for {@link #getErrorCategory}.
- */
- public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
- /**
- * The error is caused by the app requesting a function execution.
- *
- * <p>For example, the caller provided invalid parameters in the execution request e.g. an
- * invalid function ID.
- *
- * <p>Errors in the category fall in the range 1000-1999 inclusive.
- */
- public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
- /**
- * The error is caused by an issue in the system.
- *
- * <p>For example, the AppFunctionService implementation is not found by the system.
- *
- * <p>Errors in the category fall in the range 2000-2999 inclusive.
- */
- public static final int ERROR_CATEGORY_SYSTEM = 2;
-
- /**
- * The error is caused by the app providing the function.
- *
- * <p>For example, the app crashed when the system is executing the request.
- *
- * <p>Errors in the category fall in the range 3000-3999 inclusive.
- */
- public static final int ERROR_CATEGORY_APP = 3;
-
- /** The result code of the app function execution. */
- @ResultCode private final int mResultCode;
-
- /**
- * The error message associated with the result, if any. This is {@code null} if the result code
- * is {@link #RESULT_OK}.
- */
- @Nullable private final String mErrorMessage;
+ public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
/**
* Returns the return value of the executed function.
@@ -192,103 +79,21 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
/** Returns the additional metadata data relevant to this function execution response. */
@NonNull private final Bundle mExtras;
- private ExecuteAppFunctionResponse(
- @NonNull GenericDocumentWrapper resultDocumentWrapper,
- @NonNull Bundle extras,
- @ResultCode int resultCode,
- @Nullable String errorMessage) {
- mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
- mExtras = Objects.requireNonNull(extras);
- mResultCode = resultCode;
- mErrorMessage = errorMessage;
- }
-
/**
- * Returns result codes from throwable.
- *
- * @hide
- */
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
- * Returns a successful response.
- *
* @param resultDocument The return value of the executed function.
- * @param extras The additional metadata for this function execution response.
*/
- @NonNull
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- public static ExecuteAppFunctionResponse newSuccess(
- @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
- Objects.requireNonNull(resultDocument);
- Bundle actualExtras = getActualExtras(extras);
- GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument);
-
- return new ExecuteAppFunctionResponse(
- resultDocumentWrapper, actualExtras, RESULT_OK, /* errorMessage= */ null);
+ public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+ this(resultDocument, Bundle.EMPTY);
}
/**
- * Returns a failure response.
- *
- * @param resultCode The result code of the app function execution.
+ * @param resultDocument The return value of the executed function.
* @param extras The additional metadata for this function execution response.
- * @param errorMessage The error message associated with the result, if any.
- */
- @NonNull
- @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
- public static ExecuteAppFunctionResponse newFailure(
- @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
- if (resultCode == RESULT_OK) {
- throw new IllegalArgumentException("resultCode must not be RESULT_OK");
- }
- Bundle actualExtras = getActualExtras(extras);
- GenericDocumentWrapper emptyWrapper =
- new GenericDocumentWrapper(new GenericDocument.Builder<>("", "", "").build());
- return new ExecuteAppFunctionResponse(emptyWrapper, actualExtras, resultCode, errorMessage);
- }
-
- private static Bundle getActualExtras(@Nullable Bundle extras) {
- if (extras == null) {
- return Bundle.EMPTY;
- }
- return extras;
- }
-
- /**
- * Returns the error category of the {@link ExecuteAppFunctionResponse}.
- *
- * <p>This method categorizes errors based on their underlying cause, allowing developers to
- * implement targeted error handling and provide more informative error messages to users. It
- * maps ranges of result codes to specific error categories.
- *
- * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
- * ensure correct categorization of the failed response.
- *
- * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
- * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
- *
- * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
- * result code ranges.
*/
- @ErrorCategory
- public int getErrorCategory() {
- if (mResultCode >= 1000 && mResultCode < 2000) {
- return ERROR_CATEGORY_REQUEST_ERROR;
- }
- if (mResultCode >= 2000 && mResultCode < 3000) {
- return ERROR_CATEGORY_SYSTEM;
- }
- if (mResultCode >= 3000 && mResultCode < 4000) {
- return ERROR_CATEGORY_APP;
- }
- return ERROR_CATEGORY_UNKNOWN;
+ public ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+ mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument));
+ mExtras = Objects.requireNonNull(extras);
}
/**
@@ -296,9 +101,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
*
* <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
*
- * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
- * function does not produce a return value.
- *
* <p>Sample code for extracting the return value:
*
* <pre>
@@ -324,32 +126,6 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
return mExtras;
}
- /**
- * Returns {@code true} if {@link #getResultCode} equals {@link
- * ExecuteAppFunctionResponse#RESULT_OK}.
- */
- public boolean isSuccess() {
- return getResultCode() == RESULT_OK;
- }
-
- /**
- * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
- */
- @ResultCode
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the error message associated with this result.
- *
- * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
- */
- @Nullable
- public String getErrorMessage() {
- return mErrorMessage;
- }
-
@Override
public int describeContents() {
return 0;
@@ -359,43 +135,5 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
mResultDocumentWrapper.writeToParcel(dest, flags);
dest.writeBundle(mExtras);
- dest.writeInt(mResultCode);
- dest.writeString8(mErrorMessage);
}
-
- /**
- * Result codes.
- *
- * @hide
- */
- @IntDef(
- prefix = {"RESULT_"},
- value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_FUNCTION_NOT_FOUND,
- RESULT_SYSTEM_ERROR,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED,
- RESULT_CANCELLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
-
- /**
- * Error categories.
- *
- * @hide
- */
- @IntDef(
- prefix = {"ERROR_CATEGORY_"},
- value = {
- ERROR_CATEGORY_UNKNOWN,
- ERROR_CATEGORY_REQUEST_ERROR,
- ERROR_CATEGORY_APP,
- ERROR_CATEGORY_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCategory {}
}
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index b29b64e44d21..541ca7458efe 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -34,9 +34,9 @@ import java.util.Objects;
* <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
* directly or Android shared memory if the data is large.
*
- * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled
- * from the underlying `Parcel` when {@link #getValue()} is called. This optimization
- * allows the system server to pass through the generic document, without unparcel and parcel it.
+ * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled from the
+ * underlying `Parcel` when {@link #getValue()} is called. This optimization allows the system
+ * server to pass through the generic document, without unparcel and parcel it.
*
* @hide
* @see Parcel#writeBlob(byte[])
@@ -45,8 +45,11 @@ public final class GenericDocumentWrapper implements Parcelable {
@Nullable
@GuardedBy("mLock")
private GenericDocument mGenericDocument;
+
@GuardedBy("mLock")
- @Nullable private Parcel mParcel;
+ @Nullable
+ private Parcel mParcel;
+
private final Object mLock = new Object();
public static final Creator<GenericDocumentWrapper> CREATOR =
diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
index 5323f9b627e3..69bbc0e5d275 100644
--- a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
+++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
@@ -17,8 +17,10 @@
package android.app.appfunctions;
import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
/** {@hide} */
oneway interface IExecuteAppFunctionCallback {
- void onResult(in ExecuteAppFunctionResponse result);
+ void onSuccess(in ExecuteAppFunctionResponse result);
+ void onError(in AppFunctionException exception);
}
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
index 00182447e9a8..2426daf5c9f2 100644
--- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -17,17 +17,16 @@
package android.app.appfunctions;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Log;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
/**
* A wrapper of IExecuteAppFunctionCallback which swallows the {@link RemoteException}. This
- * callback is intended for one-time use only. Subsequent calls to onResult() will be ignored.
+ * callback is intended for one-time use only. Subsequent calls to onResult() or onError() will be
+ * ignored.
*
* @hide
*/
@@ -38,44 +37,41 @@ public class SafeOneTimeExecuteAppFunctionCallback {
@NonNull private final IExecuteAppFunctionCallback mCallback;
- @Nullable private final Consumer<ExecuteAppFunctionResponse> mOnDispatchCallback;
-
public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) {
- this(callback, /* onDispatchCallback= */ null);
- }
-
- /**
- * @param callback The callback to wrap.
- * @param onDispatchCallback An optional callback invoked after the wrapped callback has been
- * dispatched with a result. This callback receives the result that has been dispatched.
- */
- public SafeOneTimeExecuteAppFunctionCallback(
- @NonNull IExecuteAppFunctionCallback callback,
- @Nullable Consumer<ExecuteAppFunctionResponse> onDispatchCallback) {
mCallback = Objects.requireNonNull(callback);
- mOnDispatchCallback = onDispatchCallback;
}
/** Invoke wrapped callback with the result. */
public void onResult(@NonNull ExecuteAppFunctionResponse result) {
if (!mOnResultCalled.compareAndSet(false, true)) {
- Log.w(TAG, "Ignore subsequent calls to onResult()");
+ Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
return;
}
try {
- mCallback.onResult(result);
+ mCallback.onSuccess(result);
} catch (RemoteException ex) {
// Failed to notify the other end. Ignore.
Log.w(TAG, "Failed to invoke the callback", ex);
}
- if (mOnDispatchCallback != null) {
- mOnDispatchCallback.accept(result);
+ }
+
+ /** Invoke wrapped callback with the error. */
+ public void onError(@NonNull AppFunctionException error) {
+ if (!mOnResultCalled.compareAndSet(false, true)) {
+ Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
+ return;
+ }
+ try {
+ mCallback.onError(error);
+ } catch (RemoteException ex) {
+ // Failed to notify the other end. Ignore.
+ Log.w(TAG, "Failed to invoke the callback", ex);
}
}
/**
- * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)}
- * will be ignored.
+ * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} or
+ * {@link #onError(AppFunctionException)} will be ignored.
*/
public void disable() {
mOnResultCalled.set(true);
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index a48868906487..43a46ba7885d 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -1,5 +1,6 @@
package android.app.assist;
+import android.annotation.FlaggedApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.Intent;
@@ -15,6 +16,20 @@ import android.os.Parcelable;
* {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
*/
public class AssistContent implements Parcelable {
+ /**
+ * Extra for a {@link Bundle} that provides contextual AppFunction's information about the
+ * content currently being viewed in the application.
+ * <p>
+ * This extra can be optionally supplied in the {@link AssistContent#getExtras()} bundle.
+ * <p>
+ * The schema of the {@link Bundle} in this extra is defined in the AppFunction SDK.
+ *
+ * @see android.app.appfunctions.AppFunctionManager
+ */
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final String EXTRA_APP_FUNCTION_DATA =
+ "android.app.assist.extra.APP_FUNCTION_DATA";
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mIsAppProvidedIntent = false;
private boolean mIsAppProvidedWebUri = false;
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
index 9a645192a155..c8455c1f439f 100644
--- a/core/java/android/app/multitasking.aconfig
+++ b/core/java/android/app/multitasking.aconfig
@@ -8,3 +8,11 @@ flag {
description: "Enables PiP UI state callback on entering"
bug: "303718131"
}
+
+flag {
+ name: "enable_tv_implicit_enter_pip_restriction"
+ is_exported: true
+ namespace: "tv_system_ui"
+ description: "Enables restrictions to PiP entry on TV for setAutoEnterEnabled and lifecycle methods"
+ bug: "283115999"
+}
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index f51f748bc71f..61b53f97fea1 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -18,3 +18,20 @@ flag {
description: "Enforce PropertyInvalidatedCache.setTestMode() protocol"
bug: "360897450"
}
+
+flag {
+ namespace: "system_performance"
+ name: "pic_isolate_cache_by_uid"
+ is_fixed_read_only: true
+ description: "Ensure that different UIDs use different caches"
+ bug: "373752556"
+}
+
+flag {
+ namespace: "system_performance"
+ name: "pic_isolated_cache_statistics"
+ is_fixed_read_only: true
+ description: "Collects statistics for cache UID isolation strategies"
+ bug: "373752556"
+}
+
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 186f7b3e111c..6086f2455a31 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6802,6 +6802,12 @@ public abstract class Context {
public static final String MEDIA_QUALITY_SERVICE = "media_quality";
/**
+ * Service to perform operations needed for dynamic instrumentation.
+ * @hide
+ */
+ public static final String DYNAMIC_INSTRUMENTATION_SERVICE = "dynamic_instrumentation";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 26ecbd1982d5..f23c193e2da0 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -95,3 +95,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "layout_readwrite_flags"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for allowing read/write flags in layout files"
+ bug: "377974898"
+ # This flag is used to control aapt2 behavior.
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index ef59e0af3a27..93ef5c365730 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -45,7 +45,7 @@ import dalvik.system.CloseGuard;
* </p>
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("CursorWindow_host")
+@RavenwoodRedirectionClass("CursorWindow_ravenwood")
public class CursorWindow extends SQLiteClosable implements Parcelable {
private static final String STATS_TAG = "CursorWindowStats";
diff --git a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java b/core/java/android/database/CursorWindow_ravenwood.java
index e21a9cd71a2d..990ec5e9d59e 100644
--- a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java
+++ b/core/java/android/database/CursorWindow_ravenwood.java
@@ -17,6 +17,7 @@ package android.database;
import android.database.sqlite.SQLiteException;
import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.util.Base64;
import java.text.DecimalFormat;
@@ -26,9 +27,10 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-public class CursorWindow_host {
+@RavenwoodKeepWholeClass
+class CursorWindow_ravenwood {
- private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>();
+ private static final HashMap<Long, CursorWindow_ravenwood> sInstances = new HashMap<>();
private static long sNextId = 1;
private String mName;
@@ -41,7 +43,7 @@ public class CursorWindow_host {
private final List<Row> mRows = new ArrayList<>();
public static long nativeCreate(String name, int cursorWindowSize) {
- CursorWindow_host instance = new CursorWindow_host();
+ CursorWindow_ravenwood instance = new CursorWindow_ravenwood();
instance.mName = name;
long instanceId = sNextId++;
sInstances.put(instanceId, instance);
@@ -66,7 +68,7 @@ public class CursorWindow_host {
}
public static boolean nativeAllocRow(long windowPtr) {
- CursorWindow_host instance = sInstances.get(windowPtr);
+ CursorWindow_ravenwood instance = sInstances.get(windowPtr);
Row row = new Row();
row.mFields = new String[instance.mColumnNum];
row.mTypes = new int[instance.mColumnNum];
@@ -76,7 +78,7 @@ public class CursorWindow_host {
}
private static boolean put(long windowPtr, String value, int type, int row, int column) {
- CursorWindow_host instance = sInstances.get(windowPtr);
+ CursorWindow_ravenwood instance = sInstances.get(windowPtr);
if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
return false;
}
@@ -87,7 +89,7 @@ public class CursorWindow_host {
}
public static int nativeGetType(long windowPtr, int row, int column) {
- CursorWindow_host instance = sInstances.get(windowPtr);
+ CursorWindow_ravenwood instance = sInstances.get(windowPtr);
if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
return Cursor.FIELD_TYPE_NULL;
}
@@ -101,7 +103,7 @@ public class CursorWindow_host {
}
public static String nativeGetString(long windowPtr, int row, int column) {
- CursorWindow_host instance = sInstances.get(windowPtr);
+ CursorWindow_ravenwood instance = sInstances.get(windowPtr);
if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
return null;
}
@@ -164,7 +166,7 @@ public class CursorWindow_host {
}
public static void nativeWriteToParcel(long windowPtr, Parcel parcel) {
- CursorWindow_host window = sInstances.get(windowPtr);
+ CursorWindow_ravenwood window = sInstances.get(windowPtr);
parcel.writeString(window.mName);
parcel.writeInt(window.mColumnNum);
parcel.writeInt(window.mRows.size());
@@ -176,7 +178,7 @@ public class CursorWindow_host {
public static long nativeCreateFromParcel(Parcel parcel) {
long windowPtr = nativeCreate(null, 0);
- CursorWindow_host window = sInstances.get(windowPtr);
+ CursorWindow_ravenwood window = sInstances.get(windowPtr);
window.mName = parcel.readString();
window.mColumnNum = parcel.readInt();
int rowCount = parcel.readInt();
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 86bbd4a57a63..2f6c6a3b02d8 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3371,6 +3371,32 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_AUTOFRAMING_AUTO = 2;
//
+ // Enumeration values for CaptureRequest#CONTROL_ZOOM_METHOD
+ //
+
+ /**
+ * <p>The camera device automatically detects whether the application does zoom with
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} or {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, and in turn decides which
+ * metadata tag reflects the effective zoom level.</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CaptureRequest#CONTROL_ZOOM_METHOD
+ */
+ @FlaggedApi(Flags.FLAG_ZOOM_METHOD)
+ public static final int CONTROL_ZOOM_METHOD_AUTO = 0;
+
+ /**
+ * <p>The application intends to control zoom via {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, and
+ * the effective zoom level is reflected by {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in capture results.</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CaptureRequest#CONTROL_ZOOM_METHOD
+ */
+ @FlaggedApi(Flags.FLAG_ZOOM_METHOD)
+ public static final int CONTROL_ZOOM_METHOD_ZOOM_RATIO = 1;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
@@ -4289,6 +4315,39 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2;
+ //
+ // Enumeration values for CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+ //
+
+ /**
+ * <p>The camera can't accurately assess the scene's lighting to determine if a Night Mode
+ * Camera Extension capture would improve the photo. This can happen when the current
+ * camera configuration doesn't support night mode indicator detection, such as when
+ * the auto exposure mode is ON_AUTO_FLASH, ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or
+ * ON_EXTERNAL_FLASH.</p>
+ * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+ */
+ @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+ public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0;
+
+ /**
+ * <p>The camera has detected lighting conditions that are sufficiently bright. Night
+ * Mode Camera Extensions is available but may not be able to optimize the camera
+ * settings to take a higher quality photo.</p>
+ * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+ */
+ @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+ public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1;
+
+ /**
+ * <p>The camera has detected low-light conditions. It is recommended to use Night Mode
+ * Camera Extension to optimize the camera settings to take a high-quality photo in
+ * the dark.</p>
+ * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+ */
+ @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+ public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2;
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 8142bbe9b838..9846cac55212 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,7 +21,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.impl.ExtensionKey;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
import android.hardware.camera2.params.OutputConfiguration;
@@ -2668,6 +2667,45 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
new Key<Integer>("android.control.autoframing", int.class);
/**
+ * <p>Whether the application uses {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} or {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
+ * to control zoom levels.</p>
+ * <p>If set to AUTO, the camera device detects which capture request key the application uses
+ * to do zoom, {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} or {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. If
+ * the application doesn't set android.scaler.zoomRatio or sets it to 1.0 in the capture
+ * request, the effective zoom level is reflected in {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} in capture
+ * results. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to values other than 1.0, the effective
+ * zoom level is reflected in {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. AUTO is the default value
+ * for this control, and also the behavior of the OS before Android version
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA BAKLAVA}.</p>
+ * <p>If set to ZOOM_RATIO, the application explicitly specifies zoom level be controlled
+ * by {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, and the effective zoom level is reflected in
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in capture results. This addresses an ambiguity with AUTO,
+ * with which the camera device cannot know if the application is using cropRegion or
+ * zoomRatio at 1.0x.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_ZOOM_METHOD_AUTO AUTO}</li>
+ * <li>{@link #CONTROL_ZOOM_METHOD_ZOOM_RATIO ZOOM_RATIO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_ZOOM_METHOD_AUTO
+ * @see #CONTROL_ZOOM_METHOD_ZOOM_RATIO
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ZOOM_METHOD)
+ public static final Key<Integer> CONTROL_ZOOM_METHOD =
+ new Key<Integer>("android.control.zoomMethod", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ae72ca40fc5a..674fc662aeac 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -22,7 +22,6 @@ import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
-import android.hardware.camera2.impl.ExtensionKey;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
import android.hardware.camera2.utils.TypeReference;
@@ -2915,6 +2914,45 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.control.lowLightBoostState", int.class);
/**
+ * <p>Whether the application uses {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} or {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
+ * to control zoom levels.</p>
+ * <p>If set to AUTO, the camera device detects which capture request key the application uses
+ * to do zoom, {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} or {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. If
+ * the application doesn't set android.scaler.zoomRatio or sets it to 1.0 in the capture
+ * request, the effective zoom level is reflected in {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} in capture
+ * results. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to values other than 1.0, the effective
+ * zoom level is reflected in {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. AUTO is the default value
+ * for this control, and also the behavior of the OS before Android version
+ * {@link android.os.Build.VERSION_CODES#BAKLAVA BAKLAVA}.</p>
+ * <p>If set to ZOOM_RATIO, the application explicitly specifies zoom level be controlled
+ * by {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, and the effective zoom level is reflected in
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in capture results. This addresses an ambiguity with AUTO,
+ * with which the camera device cannot know if the application is using cropRegion or
+ * zoomRatio at 1.0x.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_ZOOM_METHOD_AUTO AUTO}</li>
+ * <li>{@link #CONTROL_ZOOM_METHOD_ZOOM_RATIO ZOOM_RATIO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_ZOOM_METHOD_AUTO
+ * @see #CONTROL_ZOOM_METHOD_ZOOM_RATIO
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_ZOOM_METHOD)
+ public static final Key<Integer> CONTROL_ZOOM_METHOD =
+ new Key<Integer>("android.control.zoomMethod", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -6016,6 +6054,38 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
public static final Key<Integer> EXTENSION_STRENGTH =
new Key<Integer>("android.extension.strength", int.class);
+ /**
+ * <p>Indicates when to activate Night Mode Camera Extension for high-quality
+ * still captures in low-light conditions.</p>
+ * <p>Provides awareness to the application when the current scene can benefit from using a
+ * Night Mode Camera Extension to take a high-quality photo.</p>
+ * <p>Support for this capture result can be queried via
+ * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p>
+ * <p>If the device supports this capability then it will also support
+ * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT}
+ * and will be available in both
+ * {@link android.hardware.camera2.CameraCaptureSession sessions} and
+ * {@link android.hardware.camera2.CameraExtensionSession sessions}.</p>
+ * <p>The value will be {@code UNKNOWN} in the following auto exposure modes: ON_AUTO_FLASH,
+ * ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or ON_EXTERNAL_FLASH.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN UNKNOWN}</li>
+ * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_OFF OFF}</li>
+ * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_ON ON}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * @see #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN
+ * @see #EXTENSION_NIGHT_MODE_INDICATOR_OFF
+ * @see #EXTENSION_NIGHT_MODE_INDICATOR_ON
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+ public static final Key<Integer> EXTENSION_NIGHT_MODE_INDICATOR =
+ new Key<Integer>("android.extension.nightModeIndicator", int.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index e22c263e893d..1cc085658bfa 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -437,7 +437,7 @@ public class CameraMetadataNative implements Parcelable {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public synchronized void writeToParcel(Parcel dest, int flags) {
nativeWriteToParcel(dest, mMetadataPtr);
}
@@ -479,7 +479,7 @@ public class CameraMetadataNative implements Parcelable {
return getBase(key);
}
- public void readFromParcel(Parcel in) {
+ public synchronized void readFromParcel(Parcel in) {
nativeReadFromParcel(in, mMetadataPtr);
updateNativeAllocation();
}
@@ -592,28 +592,33 @@ public class CameraMetadataNative implements Parcelable {
}
private <T> T getBase(Key<T> key) {
- int tag;
- if (key.hasTag()) {
- tag = key.getTag();
- } else {
- tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
- key.cacheTag(tag);
- }
- byte[] values = readValues(tag);
- if (values == null) {
- // If the key returns null, use the fallback key if exists.
- // This is to support old key names for the newly published keys.
- if (key.mFallbackName == null) {
- return null;
+ int tag, nativeType;
+ byte[] values = null;
+ synchronized (this) {
+ if (key.hasTag()) {
+ tag = key.getTag();
+ } else {
+ tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
+ key.cacheTag(tag);
}
- tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
values = readValues(tag);
if (values == null) {
- return null;
+ // If the key returns null, use the fallback key if exists.
+ // This is to support old key names for the newly published keys.
+ if (key.mFallbackName == null) {
+ return null;
+ }
+ tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
+ values = readValues(tag);
+ if (values == null) {
+ return null;
+ }
}
- }
- int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ }
+ // This block of code doesn't need to be synchronized since we aren't writing or reading
+ // from the metadata buffer for this instance of CameraMetadataNative.
Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
return marshaler.unmarshal(buffer);
@@ -1945,8 +1950,12 @@ public class CameraMetadataNative implements Parcelable {
setBase(key.getNativeKey(), value);
}
- private <T> void setBase(Key<T> key, T value) {
- int tag;
+ // The whole method needs to be synchronized since we're making
+ // multiple calls to the native layer. From one call to the other (within setBase)
+ // we expect the metadata's properties such as vendor id etc to
+ // stay the same and as a result the whole method should be synchronized for safety.
+ private synchronized <T> void setBase(Key<T> key, T value) {
+ int tag, nativeType;
if (key.hasTag()) {
tag = key.getTag();
} else {
@@ -1959,7 +1968,7 @@ public class CameraMetadataNative implements Parcelable {
return;
} // else update the entry to a new value
- int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+ nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
int size = marshaler.calculateMarshalSize(value);
@@ -2162,7 +2171,7 @@ public class CameraMetadataNative implements Parcelable {
return true;
}
- private void updateNativeAllocation() {
+ private synchronized void updateNativeAllocation() {
long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
if (currentBufferSize != mBufferSize) {
@@ -2245,6 +2254,11 @@ public class CameraMetadataNative implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>*
+ // FastNative doesn't work with synchronized methods and we can do synchronization
+ // wherever needed in the java layer (caller). At some places in java such as
+ // setBase() / getBase(), we do need to synchronize the whole method, so leaving
+ // synchronized out for these native methods.
+
@FastNative
private static native long nativeAllocate();
@FastNative
@@ -2254,28 +2268,41 @@ public class CameraMetadataNative implements Parcelable {
@FastNative
private static native void nativeUpdate(long dst, long src);
- private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr);
- private static synchronized native void nativeReadFromParcel(Parcel source, long ptr);
- private static synchronized native void nativeSwap(long ptr, long otherPtr)
+ @FastNative
+ private static native void nativeWriteToParcel(Parcel dest, long ptr);
+ @FastNative
+ private static native void nativeReadFromParcel(Parcel source, long ptr);
+ @FastNative
+ private static native void nativeSwap(long ptr, long otherPtr)
throws NullPointerException;
@FastNative
private static native void nativeSetVendorId(long ptr, long vendorId);
- private static synchronized native void nativeClose(long ptr);
- private static synchronized native boolean nativeIsEmpty(long ptr);
- private static synchronized native int nativeGetEntryCount(long ptr);
- private static synchronized native long nativeGetBufferSize(long ptr);
+ @FastNative
+ private static native void nativeClose(long ptr);
+ @FastNative
+ private static native boolean nativeIsEmpty(long ptr);
+ @FastNative
+ private static native int nativeGetEntryCount(long ptr);
+ @FastNative
+ private static native long nativeGetBufferSize(long ptr);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native byte[] nativeReadValues(int tag, long ptr);
- private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr);
- private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD
+ @FastNative
+ private static native byte[] nativeReadValues(int tag, long ptr);
+ @FastNative
+ private static native void nativeWriteValues(int tag, byte[] src, long ptr);
+ @FastNative
+ private static native void nativeDump(long ptr) throws IOException; // dump to LOGD
- private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
+ @FastNative
+ private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName)
+ @FastNative
+ private static native int nativeGetTagFromKeyLocal(long ptr, String keyName)
throws IllegalArgumentException;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag)
+ @FastNative
+ private static native int nativeGetTypeFromTagLocal(long ptr, int tag)
throws IllegalArgumentException;
@FastNative
private static native int nativeGetTagFromKey(String keyName, long vendorId)
@@ -2293,7 +2320,7 @@ public class CameraMetadataNative implements Parcelable {
* @throws NullPointerException if other was null
* @hide
*/
- public void swap(CameraMetadataNative other) {
+ public synchronized void swap(CameraMetadataNative other) {
nativeSwap(mMetadataPtr, other.mMetadataPtr);
mCameraId = other.mCameraId;
mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
@@ -2308,14 +2335,14 @@ public class CameraMetadataNative implements Parcelable {
*
* @hide
*/
- public void setVendorId(long vendorId) {
+ public synchronized void setVendorId(long vendorId) {
nativeSetVendorId(mMetadataPtr, vendorId);
}
/**
* @hide
*/
- public int getEntryCount() {
+ public synchronized int getEntryCount() {
return nativeGetEntryCount(mMetadataPtr);
}
@@ -2324,7 +2351,7 @@ public class CameraMetadataNative implements Parcelable {
*
* @hide
*/
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return nativeIsEmpty(mMetadataPtr);
}
@@ -2343,7 +2370,7 @@ public class CameraMetadataNative implements Parcelable {
*
* @hide
*/
- public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
+ public synchronized <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
if (keyClass == null) {
throw new NullPointerException();
}
@@ -2398,7 +2425,7 @@ public class CameraMetadataNative implements Parcelable {
*
* @hide
*/
- public void writeValues(int tag, byte[] src) {
+ public synchronized void writeValues(int tag, byte[] src) {
nativeWriteValues(tag, src, mMetadataPtr);
}
@@ -2413,7 +2440,7 @@ public class CameraMetadataNative implements Parcelable {
* @return {@code null} if there were 0 entries for this tag, a byte[] otherwise.
* @hide
*/
- public byte[] readValues(int tag) {
+ public synchronized byte[] readValues(int tag) {
// TODO: Optimization. Native code returns a ByteBuffer instead.
return nativeReadValues(tag, mMetadataPtr);
}
@@ -2426,7 +2453,7 @@ public class CameraMetadataNative implements Parcelable {
*
* @hide
*/
- public void dumpToLog() {
+ public synchronized void dumpToLog() {
try {
nativeDump(mMetadataPtr);
} catch (IOException e) {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a452226c81ac..28da644dd837 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,6 +16,7 @@
package android.hardware.display;
+import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.HdrCapabilities.HdrType;
import static android.view.Display.INVALID_DISPLAY;
@@ -1764,6 +1765,29 @@ public final class DisplayManager {
}
/**
+ * @return The current display topology that represents the relative positions of extended
+ * displays.
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ @Nullable
+ public DisplayTopology getDisplayTopology() {
+ return mGlobal.getDisplayTopology();
+ }
+
+ /**
+ * Set the relative positions between extended displays (display topology).
+ * @param topology The display topology to be set
+ *
+ * @hide
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void setDisplayTopology(DisplayTopology topology) {
+ mGlobal.setDisplayTopology(topology);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 644850a5c2e1..03b44f63e3b7 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,6 +18,7 @@ package android.hardware.display;
import static android.hardware.display.DisplayManager.EventFlag;
+import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
@@ -1285,6 +1286,31 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * @see DisplayManager#getDisplayTopology
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ @Nullable
+ public DisplayTopology getDisplayTopology() {
+ try {
+ return mDm.getDisplayTopology();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see DisplayManager#setDisplayTopology
+ */
+ @RequiresPermission(MANAGE_DISPLAYS)
+ public void setDisplayTopology(DisplayTopology topology) {
+ try {
+ mDm.setDisplayTopology(topology);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/DisplayTopology.aidl b/core/java/android/hardware/display/DisplayTopology.aidl
new file mode 100644
index 000000000000..e69b777a30de
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopology.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hardware.display;
+
+parcelable DisplayTopology;
diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index fdadafeb98c9..e349b81614bc 100644
--- a/services/core/java/com/android/server/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -14,25 +14,34 @@
* limitations under the License.
*/
-package com.android.server.display;
+package android.hardware.display;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.view.Display;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -42,24 +51,59 @@ import java.util.Queue;
/**
* Represents the relative placement of extended displays.
* Does not support concurrent calls, so a lock should be held when calling into this class.
+ *
+ * @hide
*/
-class DisplayTopology {
+public final class DisplayTopology implements Parcelable {
private static final String TAG = "DisplayTopology";
private static final float EPSILON = 0.0001f;
+ @android.annotation.NonNull
+ public static final Creator<DisplayTopology> CREATOR =
+ new Creator<>() {
+ @Override
+ public DisplayTopology createFromParcel(Parcel source) {
+ return new DisplayTopology(source);
+ }
+
+ @Override
+ public DisplayTopology[] newArray(int size) {
+ return new DisplayTopology[size];
+ }
+ };
+
/**
* The topology tree
*/
@Nullable
- @VisibleForTesting
- TreeNode mRoot;
+ private TreeNode mRoot;
/**
* The logical display ID of the primary display that will show certain UI elements.
* This is not necessarily the same as the default display.
*/
+ private int mPrimaryDisplayId = Display.INVALID_DISPLAY;
+
+ public DisplayTopology() {}
+
@VisibleForTesting
- int mPrimaryDisplayId = Display.INVALID_DISPLAY;
+ public DisplayTopology(TreeNode root, int primaryDisplayId) {
+ mRoot = root;
+ mPrimaryDisplayId = primaryDisplayId;
+ }
+
+ public DisplayTopology(Parcel source) {
+ this(source.readTypedObject(TreeNode.CREATOR), source.readInt());
+ }
+
+ @Nullable
+ public TreeNode getRoot() {
+ return mRoot;
+ }
+
+ public int getPrimaryDisplayId() {
+ return mPrimaryDisplayId;
+ }
/**
* Add a display to the topology.
@@ -69,7 +113,7 @@ class DisplayTopology {
* @param width The width of the display
* @param height The height of the display
*/
- void addDisplay(int displayId, float width, float height) {
+ public void addDisplay(int displayId, float width, float height) {
addDisplay(displayId, width, height, /* shouldLog= */ true);
}
@@ -79,7 +123,7 @@ class DisplayTopology {
* one by one.
* @param displayId The logical display ID
*/
- void removeDisplay(int displayId) {
+ public void removeDisplay(int displayId) {
if (findDisplay(displayId, mRoot) == null) {
return;
}
@@ -106,11 +150,22 @@ class DisplayTopology {
}
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mRoot, flags);
+ dest.writeInt(mPrimaryDisplayId);
+ }
+
/**
* Print the object's state and debug information into the given stream.
* @param pw The stream to dump information to.
*/
- void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw) {
pw.println("DisplayTopology:");
pw.println("--------------------");
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
@@ -126,13 +181,21 @@ class DisplayTopology {
}
}
+ @Override
+ public String toString() {
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ dump(writer);
+ return out.toString();
+ }
+
private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
if (findDisplay(displayId, mRoot) != null) {
throw new IllegalArgumentException(
"DisplayTopology: attempting to add a display that already exists");
}
if (mRoot == null) {
- mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
+ mRoot = new TreeNode(displayId, width, height, /* position= */ 0, /* offset= */ 0);
mPrimaryDisplayId = displayId;
if (shouldLog) {
Slog.i(TAG, "First display added: " + mRoot);
@@ -241,7 +304,7 @@ class DisplayTopology {
* Update the topology to remove any overlaps between displays.
*/
@VisibleForTesting
- void normalize() {
+ public void normalize() {
if (mRoot == null) {
return;
}
@@ -341,6 +404,8 @@ class DisplayTopology {
case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+ default -> throw new IllegalStateException(
+ "Unexpected value: " + targetDisplay.mPosition);
};
// Check that the offset is within bounds
areTouching &= switch (targetDisplay.mPosition) {
@@ -350,6 +415,8 @@ class DisplayTopology {
case POSITION_TOP, POSITION_BOTTOM ->
childBounds.right + EPSILON >= parentBounds.left
&& childBounds.left <= parentBounds.right + EPSILON;
+ default -> throw new IllegalStateException(
+ "Unexpected value: " + targetDisplay.mPosition);
};
if (!areTouching) {
@@ -379,36 +446,56 @@ class DisplayTopology {
* @param b second float to compare
* @return whether the two values are within a small enough tolerance value
*/
- public static boolean floatEquals(float a, float b) {
- return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON;
+ private static boolean floatEquals(float a, float b) {
+ return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON;
}
- @VisibleForTesting
- static class TreeNode {
+ public static final class TreeNode implements Parcelable {
+ public static final int POSITION_LEFT = 0;
+ public static final int POSITION_TOP = 1;
+ public static final int POSITION_RIGHT = 2;
+ public static final int POSITION_BOTTOM = 3;
+
+ @IntDef(prefix = { "POSITION_" }, value = {
+ POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Position{}
+
+ @android.annotation.NonNull
+ public static final Creator<TreeNode> CREATOR =
+ new Creator<>() {
+ @Override
+ public TreeNode createFromParcel(Parcel source) {
+ return new TreeNode(source);
+ }
+
+ @Override
+ public TreeNode[] newArray(int size) {
+ return new TreeNode[size];
+ }
+ };
/**
* The logical display ID
*/
- @VisibleForTesting
- final int mDisplayId;
+ private final int mDisplayId;
/**
* The width of the display in density-independent pixels (dp).
*/
- @VisibleForTesting
- float mWidth;
+ private final float mWidth;
/**
* The height of the display in density-independent pixels (dp).
*/
- @VisibleForTesting
- float mHeight;
+ private final float mHeight;
/**
* The position of this display relative to its parent.
*/
- @VisibleForTesting
- Position mPosition;
+ @Position
+ private int mPosition;
/**
* The distance from the top edge of the parent display to the top edge of this display (in
@@ -416,13 +503,13 @@ class DisplayTopology {
* to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
* used is density-independent pixels (dp).
*/
- @VisibleForTesting
- float mOffset;
+ private float mOffset;
- @VisibleForTesting
- final List<TreeNode> mChildren = new ArrayList<>();
+ private final List<TreeNode> mChildren = new ArrayList<>();
- TreeNode(int displayId, float width, float height, Position position, float offset) {
+ @VisibleForTesting
+ public TreeNode(int displayId, float width, float height, @Position int position,
+ float offset) {
mDisplayId = displayId;
mWidth = width;
mHeight = height;
@@ -430,11 +517,76 @@ class DisplayTopology {
mOffset = offset;
}
+ public TreeNode(Parcel source) {
+ this(source.readInt(), source.readFloat(), source.readFloat(), source.readInt(),
+ source.readFloat());
+ source.readTypedList(mChildren, CREATOR);
+ }
+
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ public float getWidth() {
+ return mWidth;
+ }
+
+ public float getHeight() {
+ return mHeight;
+ }
+
+ public int getPosition() {
+ return mPosition;
+ }
+
+ public float getOffset() {
+ return mOffset;
+ }
+
+ public List<TreeNode> getChildren() {
+ return Collections.unmodifiableList(mChildren);
+ }
+
+ @Override
+ public String toString() {
+ return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
+ + ", position=" + positionToString(mPosition) + ", offset=" + mOffset + "}";
+ }
+
+ /**
+ * @param position The position
+ * @return The string representation
+ */
+ public static String positionToString(@Position int position) {
+ return switch (position) {
+ case POSITION_LEFT -> "left";
+ case POSITION_TOP -> "top";
+ case POSITION_RIGHT -> "right";
+ case POSITION_BOTTOM -> "bottom";
+ default -> throw new IllegalStateException("Unexpected value: " + position);
+ };
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDisplayId);
+ dest.writeFloat(mWidth);
+ dest.writeFloat(mHeight);
+ dest.writeInt(mPosition);
+ dest.writeFloat(mOffset);
+ dest.writeTypedList(mChildren);
+ }
+
/**
* Print the object's state and debug information into the given stream.
* @param ipw The stream to dump information to.
*/
- void dump(IndentingPrintWriter ipw) {
+ public void dump(IndentingPrintWriter ipw) {
ipw.println(this);
ipw.increaseIndent();
for (TreeNode child : mChildren) {
@@ -443,15 +595,12 @@ class DisplayTopology {
ipw.decreaseIndent();
}
- @Override
- public String toString() {
- return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
- + ", position=" + mPosition + ", offset=" + mOffset + "}";
- }
-
+ /**
+ * @param child The child to add
+ */
@VisibleForTesting
- enum Position {
- POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ public void addChild(TreeNode child) {
+ mChildren.add(child);
}
}
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b612bca5671e..4fbdf7f5afc8 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -23,6 +23,7 @@ import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
import android.hardware.display.Curve;
import android.hardware.graphics.common.DisplayDecorationSupport;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.HdrConversionMode;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
@@ -254,4 +255,13 @@ interface IDisplayManager {
// Get the default doze brightness
@EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
float getDefaultDozeBrightness(int displayId);
+
+ // Get the display topology
+ @EnforcePermission("MANAGE_DISPLAYS")
+ @nullable
+ DisplayTopology getDisplayTopology();
+
+ // Set the display topology
+ @EnforcePermission("MANAGE_DISPLAYS")
+ void setDisplayTopology(in DisplayTopology topology);
}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 9d42b67bf5a8..506a19cce159 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -117,6 +117,8 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
+ public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
+ public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
public static final int FLAG_CANCELLED = 1;
@@ -203,6 +205,8 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+ KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -773,6 +777,10 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
+ case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+ return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
+ case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+ return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 38e32c61c99e..a8eb11d88aa4 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -30,14 +30,6 @@ flag {
flag {
namespace: "input_native"
- name: "emoji_and_screenshot_keycodes_available"
- is_exported: true
- description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots"
- bug: "315307777"
-}
-
-flag {
- namespace: "input_native"
name: "keyboard_a11y_slow_keys_flag"
description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
bug: "294546335"
@@ -153,6 +145,13 @@ flag {
}
flag {
+ name: "enable_new_25q2_keycodes"
+ namespace: "input"
+ description: "Enables new 25Q2 keycodes"
+ bug: "365920375"
+}
+
+flag {
name: "override_power_key_behavior_in_focused_window"
namespace: "input_native"
description: "Allows privileged focused windows to capture power key events."
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 858ec23ebed8..af715e485b73 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,7 +15,6 @@
*/
package android.hardware.location;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6284e7061b88..494bfc926384 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -18,6 +18,7 @@ package android.hardware.location;
import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,7 +32,6 @@ import android.app.ActivityThread;
import android.app.PendingIntent;
import android.chre.flags.Flags;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.contexthub.ErrorCode;
import android.os.Handler;
@@ -484,15 +484,33 @@ public final class ContextHubManager {
}
}
- /**
- * Helper function to generate a stub for a query transaction callback.
- *
- * @param transaction the transaction to unblock when complete
- *
- * @return the callback
- *
- * @hide
- */
+ /**
+ * Returns the list of HubInfo objects describing the available hubs (including ContextHub and
+ * VendorHub). This method is primarily used for debugging purposes as most clients care about
+ * endpoints and services more than hubs.
+ *
+ * @return the list of HubInfo objects
+ * @see HubInfo
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @NonNull
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public List<HubInfo> getHubs() {
+ try {
+ return mService.getHubs();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Helper function to generate a stub for a query transaction callback.
+ *
+ * @param transaction the transaction to unblock when complete
+ * @return the callback
+ * @hide
+ */
private IContextHubTransactionCallback createQueryCallback(
ContextHubTransaction<List<NanoAppState>> transaction) {
return new IContextHubTransactionCallback.Stub() {
diff --git a/core/java/android/hardware/location/HubInfo.aidl b/core/java/android/hardware/location/HubInfo.aidl
new file mode 100644
index 000000000000..25b5b0aa1222
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+/** @hide */
+parcelable HubInfo;
diff --git a/core/java/android/hardware/location/HubInfo.java b/core/java/android/hardware/location/HubInfo.java
new file mode 100644
index 000000000000..f7de1279672c
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.chre.flags.Flags;
+import android.os.BadParcelableException;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Union type for {@link ContextHubInfo} and {@link VendorHubInfo}
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class HubInfo implements Parcelable {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {TYPE_CONTEXT_HUB, TYPE_VENDOR_HUB})
+ private @interface HubType {}
+
+ public static final int TYPE_CONTEXT_HUB = 0;
+ public static final int TYPE_VENDOR_HUB = 1;
+
+ private final long mId;
+ @HubType private final int mType;
+ @Nullable private final ContextHubInfo mContextHubInfo;
+ @Nullable private final VendorHubInfo mVendorHubInfo;
+
+ /** @hide */
+ public HubInfo(long id, @NonNull ContextHubInfo contextHubInfo) {
+ mId = id;
+ mType = TYPE_CONTEXT_HUB;
+ mContextHubInfo = contextHubInfo;
+ mVendorHubInfo = null;
+ }
+
+ /** @hide */
+ public HubInfo(long id, @NonNull VendorHubInfo vendorHubInfo) {
+ mId = id;
+ mType = TYPE_VENDOR_HUB;
+ mContextHubInfo = null;
+ mVendorHubInfo = vendorHubInfo;
+ }
+
+ private HubInfo(Parcel in) {
+ mId = in.readLong();
+ mType = in.readInt();
+
+ switch (mType) {
+ case TYPE_CONTEXT_HUB:
+ mContextHubInfo = ContextHubInfo.CREATOR.createFromParcel(in);
+ mVendorHubInfo = null;
+ break;
+ case TYPE_VENDOR_HUB:
+ mVendorHubInfo = VendorHubInfo.CREATOR.createFromParcel(in);
+ mContextHubInfo = null;
+ break;
+ default:
+ throw new BadParcelableException("Parcelable has invalid type");
+ }
+ }
+
+ /** Get the hub unique identifier */
+ public long getId() {
+ return mId;
+ }
+
+ /** Get the hub type. The type can be {@link TYPE_CONTEXT_HUB} or {@link TYPE_VENDOR_HUB} */
+ public int getType() {
+ return mType;
+ }
+
+ /** Get the {@link ContextHubInfo} object, null if type is not {@link TYPE_CONTEXT_HUB} */
+ @Nullable
+ public ContextHubInfo getContextHubInfo() {
+ return mContextHubInfo;
+ }
+
+ /** Parcel implementation details */
+ public int describeContents() {
+ if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+ return mContextHubInfo.describeContents();
+ }
+ if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+ return mVendorHubInfo.describeContents();
+ }
+ return 0;
+ }
+
+ /** Parcel implementation details */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeLong(mId);
+ out.writeInt(mType);
+
+ if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+ mContextHubInfo.writeToParcel(out, flags);
+ }
+
+ if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+ mVendorHubInfo.writeToParcel(out, flags);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("HubInfo ID: 0x");
+ out.append(Long.toHexString(mId));
+ out.append("\n");
+ if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+ out.append(" ContextHubDetails: ");
+ out.append(mContextHubInfo);
+ }
+ if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+ out.append(" VendorHubDetails: ");
+ out.append(mVendorHubInfo);
+ }
+ return out.toString();
+ }
+
+ public static final @NonNull Creator<HubInfo> CREATOR =
+ new Creator<>() {
+ public HubInfo createFromParcel(Parcel in) {
+ return new HubInfo(in);
+ }
+
+ public HubInfo[] newArray(int size) {
+ return new HubInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 11f3046150d3..b0cc763dc8fd 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -18,6 +18,7 @@ package android.hardware.location;
// Declare any non-default types here with import statements
import android.app.PendingIntent;
+import android.hardware.location.HubInfo;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.NanoApp;
@@ -82,6 +83,10 @@ interface IContextHubService {
@EnforcePermission("ACCESS_CONTEXT_HUB")
List<ContextHubInfo> getContextHubs();
+ // Returns a list of HubInfo objects of available hubs (including ContextHub and VendorHub)
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ List<HubInfo> getHubs();
+
// Loads a nanoapp at the specified hub (new API)
@EnforcePermission("ACCESS_CONTEXT_HUB")
void loadNanoAppOnHub(
diff --git a/core/java/android/hardware/location/VendorHubInfo.aidl b/core/java/android/hardware/location/VendorHubInfo.aidl
new file mode 100644
index 000000000000..a7936acbb654
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+/** @hide */
+parcelable VendorHubInfo; \ No newline at end of file
diff --git a/core/java/android/hardware/location/VendorHubInfo.java b/core/java/android/hardware/location/VendorHubInfo.java
new file mode 100644
index 000000000000..26772b18176f
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.chre.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableHolder;
+
+/**
+ * Information about a VendorHub. VendorHub is similar to ContextHub, but it does not run the
+ * Context Hub Runtime Environment (or nano apps). It provides a unified endpoint messaging API
+ * through the ContextHub V4 HAL.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class VendorHubInfo implements Parcelable {
+ private final String mName;
+ private final int mVersion;
+ private final ParcelableHolder mExtendedInfo;
+
+ /** @hide */
+ public VendorHubInfo(android.hardware.contexthub.VendorHubInfo halHubInfo) {
+ mName = halHubInfo.name;
+ mVersion = halHubInfo.version;
+ mExtendedInfo = halHubInfo.extendedInfo;
+ }
+
+ private VendorHubInfo(Parcel in) {
+ mName = in.readString();
+ mVersion = in.readInt();
+ mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in);
+ }
+
+ /** Get the hub name */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Get the hub version */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /** Parcel implementation details */
+ public int describeContents() {
+ return mExtendedInfo.describeContents();
+ }
+
+ /** Parcel implementation details */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mName);
+ out.writeInt(mVersion);
+ mExtendedInfo.writeToParcel(out, flags);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("VendorHub Name : ");
+ out.append(mName);
+ out.append(", Version : ");
+ out.append(mVersion);
+ return out.toString();
+ }
+
+ public static final @NonNull Creator<VendorHubInfo> CREATOR =
+ new Creator<>() {
+ public VendorHubInfo createFromParcel(Parcel in) {
+ return new VendorHubInfo(in);
+ }
+
+ public VendorHubInfo[] newArray(int size) {
+ return new VendorHubInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index acda0c5664fe..69bd6685bcac 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -55,7 +55,7 @@ import java.util.concurrent.locks.ReentrantLock;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
public final class MessageQueue {
private static final String TAG_L = "LegacyMessageQueue";
private static final String TAG_C = "ConcurrentMessageQueue";
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 9db88d17614b..c2a47d767801 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -54,7 +54,7 @@ import java.util.concurrent.locks.ReentrantLock;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
public final class MessageQueue {
private static final String TAG = "ConcurrentMessageQueue";
private static final boolean DEBUG = false;
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 6aa9852314df..ecb5e6f1b29a 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,13 +17,17 @@
package android.os;
import android.os.CombinedVibration;
+import android.os.ICancellationSignal;
import android.os.IVibratorStateListener;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
/** {@hide} */
interface IVibratorManagerService {
int[] getVibratorIds();
+ int getCapabilities();
VibratorInfo getVibratorInfo(int vibratorId);
@EnforcePermission("ACCESS_VIBRATOR_STATE")
boolean isVibrating(int vibratorId);
@@ -50,4 +54,9 @@ interface IVibratorManagerService {
oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
int constant, int inputDeviceId, int inputSource, String reason, int flags,
int privFlags);
+
+ @EnforcePermission(allOf={"VIBRATE", "VIBRATE_VENDOR_EFFECTS", "START_VIBRATION_SESSIONS"})
+ ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
+ in int[] vibratorIds, in VibrationAttributes attributes, String reason,
+ in IVibrationSessionCallback callback);
}
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index 9f7b0b71ae95..cae82d010132 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicLong;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
public final class MessageQueue {
private static final String TAG = "MessageQueue";
private static final boolean DEBUG = false;
diff --git a/core/java/android/os/LockedMessageQueue/MessageQueue.java b/core/java/android/os/LockedMessageQueue/MessageQueue.java
index f3eec13cb20f..2401f3d11bcf 100644
--- a/core/java/android/os/LockedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LockedMessageQueue/MessageQueue.java
@@ -48,7 +48,7 @@ import java.util.concurrent.atomic.AtomicLong;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
public final class MessageQueue {
private static final String TAG = "LockedMessageQueue";
private static final boolean DEBUG = false;
diff --git a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java b/core/java/android/os/MessageQueue_ravenwood.java
index 1b63adc4319f..4033707c3253 100644
--- a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java
+++ b/core/java/android/os/MessageQueue_ravenwood.java
@@ -16,13 +16,16 @@
package android.os;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
-public class MessageQueue_host {
+@RavenwoodKeepWholeClass
+class MessageQueue_ravenwood {
private static final AtomicLong sNextId = new AtomicLong(1);
- private static final Map<Long, MessageQueue_host> sInstances = new ConcurrentHashMap<>();
+ private static final Map<Long, MessageQueue_ravenwood> sInstances = new ConcurrentHashMap<>();
private boolean mDeleted = false;
@@ -37,8 +40,8 @@ public class MessageQueue_host {
}
}
- private static MessageQueue_host getInstance(long id) {
- MessageQueue_host q = sInstances.get(id);
+ private static MessageQueue_ravenwood getInstance(long id) {
+ MessageQueue_ravenwood q = sInstances.get(id);
if (q == null) {
throw new RuntimeException("MessageQueue doesn't exist with id=" + id);
}
@@ -48,7 +51,7 @@ public class MessageQueue_host {
public static long nativeInit() {
final long id = sNextId.getAndIncrement();
- final MessageQueue_host q = new MessageQueue_host();
+ final MessageQueue_ravenwood q = new MessageQueue_ravenwood();
sInstances.put(id, q);
return id;
}
diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
index db323dcf9009..435c34f832c6 100644
--- a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
@@ -52,7 +52,7 @@ import java.util.concurrent.atomic.AtomicLong;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
public final class MessageQueue {
private static final String TAG = "SemiConcurrentMessageQueue";
private static final boolean DEBUG = false;
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 011a3ee91ada..c3cddf32f063 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,8 +18,11 @@ package android.os;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.vibrator.VendorVibrationSession;
import android.os.vibrator.VibratorInfoFactory;
import android.util.ArrayMap;
import android.util.Log;
@@ -53,6 +56,7 @@ public class SystemVibrator extends Vibrator {
private final Object mLock = new Object();
@GuardedBy("mLock")
private VibratorInfo mVibratorInfo;
+ private int[] mVibratorIds;
@UnsupportedAppUsage
public SystemVibrator(Context context) {
@@ -71,7 +75,11 @@ public class SystemVibrator extends Vibrator {
Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager.");
return VibratorInfo.EMPTY_VIBRATOR_INFO;
}
- int[] vibratorIds = mVibratorManager.getVibratorIds();
+ int[] vibratorIds = getVibratorIds();
+ if (vibratorIds == null) {
+ Log.w(TAG, "Failed to retrieve vibrator info; error retrieving vibrator ids.");
+ return VibratorInfo.EMPTY_VIBRATOR_INFO;
+ }
if (vibratorIds.length == 0) {
// It is known that the device has no vibrator, so cache and return info that
// reflects the lack of support for effects/primitives.
@@ -95,20 +103,22 @@ public class SystemVibrator extends Vibrator {
@Override
public boolean hasVibrator() {
- if (mVibratorManager == null) {
+ int[] vibratorIds = getVibratorIds();
+ if (vibratorIds == null) {
Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
return false;
}
- return mVibratorManager.getVibratorIds().length > 0;
+ return vibratorIds.length > 0;
}
@Override
public boolean isVibrating() {
- if (mVibratorManager == null) {
+ int[] vibratorIds = getVibratorIds();
+ if (vibratorIds == null) {
Log.w(TAG, "Failed to vibrate; no vibrator manager.");
return false;
}
- for (int vibratorId : mVibratorManager.getVibratorIds()) {
+ for (int vibratorId : vibratorIds) {
if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
return true;
}
@@ -136,6 +146,11 @@ public class SystemVibrator extends Vibrator {
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
return;
}
+ int[] vibratorIds = getVibratorIds();
+ if (vibratorIds == null) {
+ Log.w(TAG, "Failed to add vibrate state listener; error retrieving vibrator ids.");
+ return;
+ }
MultiVibratorStateListener delegate = null;
try {
synchronized (mRegisteredListeners) {
@@ -145,7 +160,7 @@ public class SystemVibrator extends Vibrator {
return;
}
delegate = new MultiVibratorStateListener(executor, listener);
- delegate.register(mVibratorManager);
+ delegate.register(mVibratorManager, vibratorIds);
mRegisteredListeners.put(listener, delegate);
delegate = null;
}
@@ -184,6 +199,11 @@ public class SystemVibrator extends Vibrator {
}
@Override
+ public boolean areVendorSessionsSupported() {
+ return mVibratorManager.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ }
+
+ @Override
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
VibrationAttributes attrs) {
if (mVibratorManager == null) {
@@ -243,6 +263,41 @@ public class SystemVibrator extends Vibrator {
mVibratorManager.cancel(usageFilter);
}
+ @Override
+ public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
+ @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+ @NonNull VendorVibrationSession.Callback callback) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to start vibration session; no vibrator manager.");
+ executor.execute(
+ () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
+ return;
+ }
+ int[] vibratorIds = getVibratorIds();
+ if (vibratorIds == null) {
+ Log.w(TAG, "Failed to start vibration session; error retrieving vibrator ids.");
+ executor.execute(
+ () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
+ return;
+ }
+ mVibratorManager.startVendorSession(vibratorIds, attrs, reason, cancellationSignal,
+ executor, callback);
+ }
+
+ @Nullable
+ private int[] getVibratorIds() {
+ synchronized (mLock) {
+ if (mVibratorIds != null) {
+ return mVibratorIds;
+ }
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager.");
+ return null;
+ }
+ return mVibratorIds = mVibratorManager.getVibratorIds();
+ }
+ }
+
/**
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
* that were left registered to vibrators after failures to register them to all vibrators.
@@ -319,8 +374,7 @@ public class SystemVibrator extends Vibrator {
}
/** Registers a listener to all individual vibrators in {@link VibratorManager}. */
- public void register(VibratorManager vibratorManager) {
- int[] vibratorIds = vibratorManager.getVibratorIds();
+ public void register(VibratorManager vibratorManager, @NonNull int[] vibratorIds) {
synchronized (mLock) {
for (int i = 0; i < vibratorIds.length; i++) {
int vibratorId = vibratorIds[i];
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index a5697fb0e8a8..f9935d2870b0 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -22,6 +22,10 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
+import android.os.vibrator.VendorVibrationSession;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
@@ -47,6 +51,8 @@ public class SystemVibratorManager extends VibratorManager {
@GuardedBy("mLock")
private int[] mVibratorIds;
@GuardedBy("mLock")
+ private int mCapabilities;
+ @GuardedBy("mLock")
private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
@GuardedBy("mLock")
@@ -84,6 +90,11 @@ public class SystemVibratorManager extends VibratorManager {
}
}
+ @Override
+ public boolean hasCapabilities(int capabilities) {
+ return (getCapabilities() & capabilities) == capabilities;
+ }
+
@NonNull
@Override
public Vibrator getVibrator(int vibratorId) {
@@ -173,7 +184,7 @@ public class SystemVibratorManager extends VibratorManager {
int inputSource, String reason, int flags, int privFlags) {
if (mService == null) {
Log.w(TAG, "Failed to perform haptic feedback for input device;"
- + " no vibrator manager service.");
+ + " no vibrator manager service.");
return;
}
Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
@@ -197,6 +208,50 @@ public class SystemVibratorManager extends VibratorManager {
cancelVibration(usageFilter);
}
+ @Override
+ public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs,
+ @Nullable String reason, @Nullable CancellationSignal cancellationSignal,
+ @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) {
+ Objects.requireNonNull(vibratorIds);
+ VendorVibrationSessionCallbackDelegate callbackDelegate =
+ new VendorVibrationSessionCallbackDelegate(executor, callback);
+ if (mService == null) {
+ Log.w(TAG, "Failed to start vibration session; no vibrator manager service.");
+ callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
+ return;
+ }
+ try {
+ ICancellationSignal remoteCancellationSignal = mService.startVendorVibrationSession(
+ mUid, mContext.getDeviceId(), mPackageName, vibratorIds, attrs, reason,
+ callbackDelegate);
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to start vibration session.", e);
+ callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
+ }
+ }
+
+ private int getCapabilities() {
+ synchronized (mLock) {
+ if (mCapabilities != 0) {
+ return mCapabilities;
+ }
+ try {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve vibrator manager capabilities;"
+ + " no vibrator manager service.");
+ } else {
+ return mCapabilities = mService.getCapabilities();
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return 0;
+ }
+ }
+
private void cancelVibration(int usageFilter) {
if (mService == null) {
Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
@@ -228,12 +283,45 @@ public class SystemVibratorManager extends VibratorManager {
}
}
+ /** Callback for vendor vibration sessions. */
+ private static class VendorVibrationSessionCallbackDelegate extends
+ IVibrationSessionCallback.Stub {
+ private final Executor mExecutor;
+ private final VendorVibrationSession.Callback mCallback;
+
+ VendorVibrationSessionCallbackDelegate(
+ @NonNull Executor executor,
+ @NonNull VendorVibrationSession.Callback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStarted(IVibrationSession session) {
+ mExecutor.execute(() -> mCallback.onStarted(new VendorVibrationSession(session)));
+ }
+
+ @Override
+ public void onFinishing() {
+ mExecutor.execute(() -> mCallback.onFinishing());
+ }
+
+ @Override
+ public void onFinished(int status) {
+ mExecutor.execute(() -> mCallback.onFinished(status));
+ }
+ }
+
/** Controls vibrations on a single vibrator. */
private final class SingleVibrator extends Vibrator {
private final VibratorInfo mVibratorInfo;
+ private final int[] mVibratorId;
SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
mVibratorInfo = vibratorInfo;
+ mVibratorId = new int[]{mVibratorInfo.getId()};
}
@Override
@@ -252,6 +340,11 @@ public class SystemVibratorManager extends VibratorManager {
}
@Override
+ public boolean areVendorSessionsSupported() {
+ return SystemVibratorManager.this.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ }
+
+ @Override
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
@Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) {
CombinedVibration combined = CombinedVibration.startParallel()
@@ -369,5 +462,13 @@ public class SystemVibratorManager extends VibratorManager {
}
}
}
+
+ @Override
+ public void startVendorSession(@NonNull VibrationAttributes attrs, String reason,
+ @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+ @NonNull VendorVibrationSession.Callback callback) {
+ SystemVibratorManager.this.startVendorSession(mVibratorId, attrs, reason,
+ cancellationSignal, executor, callback);
+ }
}
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index c4c4580bf0a8..53f8a9267499 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -33,6 +33,7 @@ import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.vibrator.Flags;
+import android.os.vibrator.VendorVibrationSession;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibratorFrequencyProfile;
import android.os.vibrator.VibratorFrequencyProfileLegacy;
@@ -247,6 +248,34 @@ public abstract class Vibrator {
}
/**
+ * Check whether the vibrator has support for vendor-specific effects.
+ *
+ * <p>Vendor vibration effects can be created via {@link VibrationEffect#createVendorEffect}.
+ *
+ * @return True if the hardware can play vendor-specific vibration effects, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public boolean areVendorEffectsSupported() {
+ return getInfo().hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+ }
+
+ /**
+ * Check whether the vibrator has support for vendor-specific vibration sessions.
+ *
+ * <p>Vendor vibration sessions can be started via {@link #startVendorSession}.
+ *
+ * @return True if the hardware can play vendor-specific vibration sessions, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public boolean areVendorSessionsSupported() {
+ return false;
+ }
+
+ /**
* Check whether the vibrator can be controlled by an external service with the
* {@link IExternalVibratorService}.
*
@@ -922,4 +951,44 @@ public abstract class Vibrator {
@RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
}
+
+ /**
+ * Starts a vibration session in this vibrator.
+ *
+ * <p>The session will start asynchronously once the vibrator control can be acquired. Once it's
+ * started the {@link VendorVibrationSession} will be provided to the callback. This session
+ * should be used to play vibrations until the session is ended or canceled.
+ *
+ * <p>The vendor app will have exclusive control over the vibrator during this session. This
+ * control can be revoked by the vibrator service, which will be notified to the same session
+ * callback with the {@link VendorVibrationSession#STATUS_CANCELED}.
+ *
+ * <p>The {@link VibrationAttributes} will be used to decide the priority of the vendor
+ * vibrations that will be performed in this session. All vibrations within this session will
+ * apply the same attributes.
+ *
+ * @param attrs The {@link VibrationAttributes} corresponding to the vibrations that will be
+ * performed in the session. This will be used to decide the priority of this
+ * session against other system vibrations.
+ * @param reason The description for this session, used for debugging purposes.
+ * @param cancellationSignal A signal to cancel the session before it starts.
+ * @param executor The executor for the session callbacks.
+ * @param callback The {@link VendorVibrationSession.Callback} for the started session.
+ *
+ * @see VendorVibrationSession
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ android.Manifest.permission.START_VIBRATION_SESSIONS,
+ })
+ public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
+ @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+ @NonNull VendorVibrationSession.Callback callback) {
+ Log.w(TAG, "startVendorSession is not supported");
+ executor.execute(() -> callback.onFinished(VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
}
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 0428876891f9..0072bc22ad8f 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -22,9 +22,12 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.ActivityThread;
import android.content.Context;
+import android.os.vibrator.VendorVibrationSession;
import android.util.Log;
import android.view.HapticFeedbackConstants;
+import java.util.concurrent.Executor;
+
/**
* Provides access to all vibrators from the device, as well as the ability to run them
* in a synchronized fashion.
@@ -62,6 +65,14 @@ public abstract class VibratorManager {
public abstract int[] getVibratorIds();
/**
+ * Return true if the vibrator manager has all capabilities, false otherwise.
+ * @hide
+ */
+ public boolean hasCapabilities(int capabilities) {
+ return false;
+ }
+
+ /**
* Retrieve a single vibrator by id.
*
* @param vibratorId The id of the vibrator to be retrieved.
@@ -190,4 +201,30 @@ public abstract class VibratorManager {
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public abstract void cancel(int usageFilter);
+
+
+ /**
+ * Starts a vibration session on given vibrators.
+ *
+ * @param vibratorIds The vibrators that will be controlled by this session.
+ * @param attrs The {@link VibrationAttributes} corresponding to the vibrations that will
+ * be performed in the session. This will be used to decide the priority of
+ * this session against other system vibrations.
+ * @param reason The description for this session, used for debugging purposes.
+ * @param cancellationSignal A signal to cancel the session before it starts.
+ * @param executor The executor for the session callbacks.
+ * @param callback The {@link VendorVibrationSession.Callback} for the started session.
+ * @see Vibrator#startVendorSession
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ android.Manifest.permission.START_VIBRATION_SESSIONS,
+ })
+ public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs,
+ @Nullable String reason, @Nullable CancellationSignal cancellationSignal,
+ @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) {
+ Log.w(TAG, "startVendorSession is not supported");
+ }
}
diff --git a/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
new file mode 100644
index 000000000000..dbe54891b0f2
--- /dev/null
+++ b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.instrumentation;
+
+/**
+ * Represents the location of the code for a compiled method within a process'
+ * memory.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable ExecutableMethodFileOffsets {
+ /**
+ * The OS path of the containing file (could be virtual).
+ */
+ @utf8InCpp String containerPath;
+ /**
+ * The offset of the containing file within the process' memory.
+ */
+ long containerOffset;
+ /**
+ * The offset of the method within the containing file.
+ */
+ long methodOffset;
+}
diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
new file mode 100644
index 000000000000..c45c51d15cc9
--- /dev/null
+++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+/**
+ * System private API for managing the dynamic attachment of instrumentation.
+ *
+ * {@hide}
+ */
+interface IDynamicInstrumentationManager {
+ /** Provides ART metadata about the described compiled method within the target process */
+ @PermissionManuallyEnforced
+ @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+ in TargetProcess targetProcess, in MethodDescriptor methodDescriptor);
+}
diff --git a/core/java/android/os/instrumentation/MethodDescriptor.aidl b/core/java/android/os/instrumentation/MethodDescriptor.aidl
new file mode 100644
index 000000000000..055d0ecb66e4
--- /dev/null
+++ b/core/java/android/os/instrumentation/MethodDescriptor.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+/**
+ * Represents a JVM method, where class fields that make up its signature.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable MethodDescriptor {
+ /**
+ * Fully qualified class in reverse.domain.Naming
+ */
+ @utf8InCpp String fullyQualifiedClassName;
+ /**
+ * Name of the method.
+ */
+ @utf8InCpp String methodName;
+ /**
+ * Fully qualified types of method parameters, or string representations if primitive e.g. "int".
+ */
+ @utf8InCpp String[] fullyQualifiedParameters;
+}
diff --git a/core/java/android/os/instrumentation/TargetProcess.aidl b/core/java/android/os/instrumentation/TargetProcess.aidl
new file mode 100644
index 000000000000..e90780d07ef2
--- /dev/null
+++ b/core/java/android/os/instrumentation/TargetProcess.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+/**
+ * Addresses a process that would run on the device.
+ * Helps disambiguate targeted processes in cases of pid re-use.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable TargetProcess {
+ int uid;
+ int pid;
+ @utf8InCpp String processName;
+}
diff --git a/core/java/android/os/vibrator/IVibrationSession.aidl b/core/java/android/os/vibrator/IVibrationSession.aidl
new file mode 100644
index 000000000000..e8295492665d
--- /dev/null
+++ b/core/java/android/os/vibrator/IVibrationSession.aidl
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.vibrator;
+
+import android.os.CombinedVibration;
+
+/**
+ * The communication channel by which an app control the system vibrators.
+ *
+ * In order to synchronize the places where vibrations might be controlled we provide this interface
+ * so the vibrator subsystem has a chance to:
+ *
+ * 1) Decide whether the current session should have the vibrator control.
+ * 2) Stop any on-going session for a new session/vibration, based on current system policy.
+ * {@hide}
+ */
+interface IVibrationSession {
+ const int STATUS_UNKNOWN = 0;
+ const int STATUS_SUCCESS = 1;
+ const int STATUS_IGNORED = 2;
+ const int STATUS_UNSUPPORTED = 3;
+ const int STATUS_CANCELED = 4;
+ const int STATUS_UNKNOWN_ERROR = 5;
+
+ /**
+ * A method called to start a vibration within this session. This will fail if the session
+ * is finishing or was canceled.
+ */
+ void vibrate(in CombinedVibration vibration, String reason);
+
+ /**
+ * A method called by the app to stop this session gracefully. The vibrator will complete any
+ * ongoing vibration before the session is ended.
+ */
+ void finishSession();
+
+ /**
+ * A method called by the app to stop this session immediatelly by interrupting any ongoing
+ * vibration.
+ */
+ void cancelSession();
+}
diff --git a/core/java/android/os/vibrator/IVibrationSessionCallback.aidl b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl
new file mode 100644
index 000000000000..36c3695a1bfe
--- /dev/null
+++ b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl
@@ -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 android.os.vibrator;
+
+import android.os.vibrator.IVibrationSession;
+
+/**
+ * Callback for vibration session state.
+ * {@hide}
+ */
+oneway interface IVibrationSessionCallback {
+
+ /**
+ * A method called by the service after a vibration session has successfully started. After this
+ * is called the app has control over the vibrator through this given session.
+ */
+ void onStarted(in IVibrationSession session);
+
+ /**
+ * A method called by the service to indicate the session is ending and should no longer receive
+ * vibration requests.
+ */
+ void onFinishing();
+
+ /**
+ * A method called by the service after the session has ended. This might be triggered by the
+ * app or the service. The status code indicates the end reason.
+ */
+ void onFinished(int status);
+}
diff --git a/core/java/android/os/vibrator/VendorVibrationSession.java b/core/java/android/os/vibrator/VendorVibrationSession.java
new file mode 100644
index 000000000000..c23f2ed1a303
--- /dev/null
+++ b/core/java/android/os/vibrator/VendorVibrationSession.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.CombinedVibration;
+import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A vendor session that temporarily gains control over the system vibrators.
+ *
+ * <p>Vibration effects can be played by the vibrator in a vendor session via {@link #vibrate}. The
+ * effects will be forwarded to the vibrator hardware immediately. Any concurrency support is
+ * defined and controlled by the vibrator hardware implementation.
+ *
+ * <p>The session should be ended by {@link #close()}, which will wait until the last vibration ends
+ * and the vibrator is released. The end of the session will be notified to the {@link Callback}
+ * provided when the session was created.
+ *
+ * <p>Any ongoing session can be immediately interrupted by the vendor app via {@link #cancel()},
+ * including after {@link #close()} was called and the session is tearing down. A session can also
+ * be canceled by the vibrator service when it needs to regain control of the system vibrators.
+ *
+ * @see Vibrator#startVendorSession
+ * @hide
+ */
+@FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+@SystemApi
+public final class VendorVibrationSession implements AutoCloseable {
+ private static final String TAG = "VendorVibrationSession";
+
+ /**
+ * The session ended successfully.
+ */
+ public static final int STATUS_SUCCESS = IVibrationSession.STATUS_SUCCESS;
+
+ /**
+ * The session was ignored.
+ *
+ * <p>This might be caused by user settings, vibration policies or the device state that
+ * prevents the app from performing vibrations for the requested
+ * {@link android.os.VibrationAttributes}.
+ */
+ public static final int STATUS_IGNORED = IVibrationSession.STATUS_IGNORED;
+
+ /**
+ * The session is not supported.
+ *
+ * <p>The support for vendor vibration sessions can be checked via
+ * {@link Vibrator#areVendorSessionsSupported()}.
+ */
+ public static final int STATUS_UNSUPPORTED = IVibrationSession.STATUS_UNSUPPORTED;
+
+ /**
+ * The session was canceled.
+ *
+ * <p>This might be triggered by the app after a session starts via {@link #cancel()}, or it
+ * can be triggered by the platform before or after the session has started.
+ */
+ public static final int STATUS_CANCELED = IVibrationSession.STATUS_CANCELED;
+
+ /**
+ * The session status is unknown.
+ */
+ public static final int STATUS_UNKNOWN = IVibrationSession.STATUS_UNKNOWN;
+
+ /**
+ * The session failed with unknown error.
+ *
+ * <p>This can be caused by a failure to start a vibration session or after it has started, to
+ * indicate it has ended unexpectedly because of a system failure.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = IVibrationSession.STATUS_UNKNOWN_ERROR;
+
+ /** @hide */
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_SUCCESS,
+ STATUS_IGNORED,
+ STATUS_UNSUPPORTED,
+ STATUS_CANCELED,
+ STATUS_UNKNOWN,
+ STATUS_UNKNOWN_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status{}
+
+ private final IVibrationSession mSession;
+
+ /** @hide */
+ public VendorVibrationSession(@NonNull IVibrationSession session) {
+ Objects.requireNonNull(session);
+ mSession = session;
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The vibration will be sent to the vibrator hardware immediately, without waiting for any
+ * previous vibration completion. The vendor should control the concurrency behavior at the
+ * hardware level (e.g. queueing, mixing, interrupting).
+ *
+ * <p>If the provided effect is played by the vibrator service with controlled timings (e.g.
+ * effects created via {@link VibrationEffect#createWaveform}), then triggering a new vibration
+ * will cause the ongoing playback to be interrupted in favor of the new vibration. If the
+ * effect is broken down into multiple consecutive commands (e.g. large primitive compositions)
+ * then the hardware commands will be triggered in succession without waiting for the completion
+ * callback.
+ *
+ * <p>The vendor app is responsible for timing the session requests and the vibrator hardware
+ * implementation is free to handle concurrency with different policies.
+ *
+ * @param effect The {@link VibrationEffect} describing the vibration to be performed.
+ * @param reason The description for the vibration reason, for debugging purposes.
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect effect, @Nullable String reason) {
+ try {
+ mSession.vibrate(CombinedVibration.createParallel(effect), reason);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate in a vendor vibration session.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Cancel ongoing session.
+ *
+ * <p>This will stop the vibration immediately and return the vibrator control to the
+ * platform. This can also be triggered after {@link #close()} to immediately release the
+ * vibrator.
+ *
+ * <p>This will trigger {@link VendorVibrationSession.Callback#onFinished} directly with
+ * {@link #STATUS_CANCELED}.
+ */
+ public void cancel() {
+ try {
+ mSession.cancelSession();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vendor vibration session.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * End ongoing session gracefully.
+ *
+ * <p>This might continue the vibration while it's ramping down and wrapping up the session
+ * in the vibrator hardware. No more vibration commands can be sent through this session
+ * after this method is called.
+ *
+ * <p>This will trigger {@link VendorVibrationSession.Callback#onFinishing()}.
+ */
+ @Override
+ public void close() {
+ try {
+ mSession.finishSession();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to finish vendor vibration session.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Callbacks for {@link VendorVibrationSession} events.
+ *
+ * @see Vibrator#startVendorSession
+ * @see VendorVibrationSession
+ */
+ public interface Callback {
+
+ /**
+ * New session was successfully started.
+ *
+ * <p>The vendor app can interact with the vibrator using the
+ * {@link VendorVibrationSession} provided.
+ */
+ void onStarted(@NonNull VendorVibrationSession session);
+
+ /**
+ * The session is ending and finishing any pending vibrations.
+ *
+ * <p>This is only invoked after {@link #onStarted(VendorVibrationSession)}. It will be
+ * triggered by both {@link VendorVibrationSession#cancel()} and
+ * {@link VendorVibrationSession#close()}. This might also be triggered if the platform
+ * cancels the ongoing session.
+ *
+ * <p>Session vibrations might be still ongoing in the vibrator hardware but the app can
+ * no longer send commands through the session. A finishing session can still be immediately
+ * stopped via calls to {@link VendorVibrationSession.Callback#cancel()}.
+ */
+ void onFinishing();
+
+ /**
+ * The session is finished.
+ *
+ * <p>The vibrator has finished any vibration and returned to the platform's control. This
+ * might be triggered by the vendor app or by the vibrator service.
+ *
+ * <p>If this is triggered before {@link #onStarted} then the session was finished before
+ * starting, either because it was cancelled or failed to start. If the session has already
+ * started then this will be triggered after {@link #onFinishing()} to indicate all session
+ * vibrations are complete and the vibrator is no longer under the session's control.
+ *
+ * @param status The session status.
+ */
+ void onFinished(@VendorVibrationSession.Status int status);
+ }
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 9e0d0e195a96..60a0ae3f107d 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -354,9 +354,10 @@ flag {
flag {
name: "health_connect_backup_restore_permission_enabled"
is_fixed_read_only: true
- namespace: "health_connect"
+ namespace: "health_fitness_aconfig"
description: "This flag protects the permission that is required to call Health Connect backup and restore apis"
bug: "376014879" # android_fr bug
+ is_exported: true
}
flag {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d19681c86320..ef351719ea70 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8687,6 +8687,19 @@ public final class Settings {
public static final String ACCESSIBILITY_QS_TARGETS = "accessibility_qs_targets";
/**
+ * Setting specifying the accessibility services, accessibility shortcut targets,
+ * or features to be toggled via a keyboard shortcut gesture.
+ *
+ * <p> This is a colon-separated string list which contains the flattened
+ * {@link ComponentName} and the class name of a system class implementing a supported
+ * accessibility feature.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_KEY_GESTURE_TARGETS =
+ "accessibility_key_gesture_targets";
+
+ /**
* The system class name of magnification controller which is a target to be toggled via
* accessibility shortcut or accessibility button.
*
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1d35344e5218..7cb0ffcfcc72 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -120,3 +120,10 @@ flag {
description: "Feature flag for exposing KeyStore grant APIs"
bug: "351158708"
}
+
+flag {
+ name: "secure_lockdown"
+ namespace: "biometrics"
+ description: "Feature flag for Secure Lockdown feature"
+ bug: "373422357"
+} \ No newline at end of file
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 72f2de805474..dfc11dcb5427 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -67,3 +67,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "cleanup_dream_settings_on_uninstall"
+ namespace: "systemui"
+ description: "Cleans up dream settings if dream package is uninstalled."
+ bug: "338210427"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.aidl b/core/java/android/service/settings/preferences/GetValueRequest.aidl
new file mode 100644
index 000000000000..2a0eb09aa2a4
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable GetValueRequest; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java
new file mode 100644
index 000000000000..4f82800d1855
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueRequest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Request parameters to retrieve the current value of a Settings Preference.
+ *
+ * <p>This object passed to {@link SettingsPreferenceService#onGetPreferenceValue} will result
+ * in a {@link GetValueResult}.
+ *
+ * <ul>
+ * <li>{@link #getScreenKey} is a parameter to distinguish the container screen
+ * of a preference as a preference key may not be unique within its application.
+ * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is
+ * being requested. These keys will be unique with their Preference Screen, but may not be unique
+ * within their application, so it is required to pair this with {@link #getScreenKey} to
+ * ensure this request matches the intended target.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class GetValueRequest implements Parcelable {
+
+ @NonNull
+ private final String mScreenKey;
+ @NonNull
+ private final String mPreferenceKey;
+
+ /**
+ * Returns the screen key of requested Preference.
+ */
+ @NonNull
+ public String getScreenKey() {
+ return mScreenKey;
+ }
+
+ /**
+ * Returns the key of requested Preference.
+ */
+ @NonNull
+ public String getPreferenceKey() {
+ return mPreferenceKey;
+ }
+
+ private GetValueRequest(@NonNull Builder builder) {
+ mScreenKey = builder.mScreenKey;
+ mPreferenceKey = builder.mPreferenceKey;
+ }
+
+ private GetValueRequest(@NonNull Parcel in) {
+ mScreenKey = Objects.requireNonNull(in.readString8());
+ mPreferenceKey = Objects.requireNonNull(in.readString8());
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mScreenKey);
+ dest.writeString8(mPreferenceKey);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link GetValueRequest}.
+ */
+ @NonNull
+ public static final Creator<GetValueRequest> CREATOR = new Creator<GetValueRequest>() {
+ @Override
+ public GetValueRequest createFromParcel(@NonNull Parcel in) {
+ return new GetValueRequest(in);
+ }
+
+ @Override
+ public GetValueRequest[] newArray(int size) {
+ return new GetValueRequest[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link GetValueRequest}.
+ */
+ public static final class Builder {
+ private final String mScreenKey;
+ private final String mPreferenceKey;
+
+ /**
+ * Create Builder instance.
+ * @param screenKey required to be not empty
+ * @param preferenceKey required to be not empty
+ */
+ public Builder(@NonNull String screenKey, @NonNull String preferenceKey) {
+ if (TextUtils.isEmpty(screenKey)) {
+ throw new IllegalArgumentException("screenKey cannot be empty");
+ }
+ if (TextUtils.isEmpty(preferenceKey)) {
+ throw new IllegalArgumentException("preferenceKey cannot be empty");
+ }
+ mScreenKey = screenKey;
+ mPreferenceKey = preferenceKey;
+ }
+
+ /**
+ * Constructs an immutable {@link GetValueRequest} object.
+ */
+ @NonNull
+ public GetValueRequest build() {
+ return new GetValueRequest(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/GetValueResult.aidl b/core/java/android/service/settings/preferences/GetValueResult.aidl
new file mode 100644
index 000000000000..b5ebd35a3a37
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable GetValueResult; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java
new file mode 100644
index 000000000000..369dea77cc85
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueResult.java
@@ -0,0 +1,213 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result object given a corresponding {@link GetValueRequest}.
+ * <ul>
+ * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK},
+ * {@link #getValue} will be populated with the settings preference value and
+ * {@link #getMetadata} will be populated with its metadata.
+ * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ * of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class GetValueResult implements Parcelable {
+
+ @ResultCode
+ private final int mResultCode;
+ @Nullable
+ private final SettingsPreferenceValue mValue;
+ @Nullable
+ private final SettingsPreferenceMetadata mMetadata;
+
+ /**
+ * Returns the result code indicating status of the request.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Returns the value of requested Preference if request successful.
+ */
+ @Nullable
+ public SettingsPreferenceValue getValue() {
+ return mValue;
+ }
+
+ /**
+ * Returns the metadata of requested Preference if request successful.
+ */
+ @Nullable
+ public SettingsPreferenceMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_OK,
+ RESULT_UNSUPPORTED,
+ RESULT_UNAVAILABLE,
+ RESULT_REQUIRE_APP_PERMISSION,
+ RESULT_DISALLOW,
+ RESULT_INVALID_REQUEST,
+ RESULT_INTERNAL_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {
+ }
+
+ /** Request is successful. */
+ public static final int RESULT_OK = 0;
+ /**
+ * Requested preference is not supported by this API.
+ * <p>Retry not advised.
+ */
+ public static final int RESULT_UNSUPPORTED = 1;
+ /**
+ * Preference is currently not available, likely due to device state or the state of
+ * a dependency.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_UNAVAILABLE = 2;
+ /**
+ * Requested preference requires permissions not held by the calling application.
+ * <p>Retry may succeed if necessary permissions are obtained.
+ */
+ public static final int RESULT_REQUIRE_APP_PERMISSION = 3;
+ /**
+ * Requested preference is not allowed for access in this API under the current device policy.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_DISALLOW = 4;
+ /**
+ * Request object is not valid.
+ * <p>Retry not advised with current parameters.
+ */
+ public static final int RESULT_INVALID_REQUEST = 5;
+ /**
+ * API call failed due to an issue with the service binding.
+ * <p>Retry may succeed.
+ */
+ public static final int RESULT_INTERNAL_ERROR = 6;
+
+
+ private GetValueResult(@NonNull Builder builder) {
+ mResultCode = builder.mResultCode;
+ mValue = builder.mValue;
+ mMetadata = builder.mMetadata;
+ }
+
+ private GetValueResult(@NonNull Parcel in) {
+ mResultCode = in.readInt();
+ mValue = in.readParcelable(SettingsPreferenceValue.class.getClassLoader(),
+ SettingsPreferenceValue.class);
+ mMetadata = in.readParcelable(SettingsPreferenceMetadata.class.getClassLoader(),
+ SettingsPreferenceMetadata.class);
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mResultCode);
+ dest.writeParcelable(mValue, flags);
+ dest.writeParcelable(mMetadata, flags);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link GetValueResult}.
+ */
+ @NonNull
+ public static final Creator<GetValueResult> CREATOR = new Creator<>() {
+ @Override
+ public GetValueResult createFromParcel(@NonNull Parcel in) {
+ return new GetValueResult(in);
+ }
+
+ @Override
+ public GetValueResult[] newArray(int size) {
+ return new GetValueResult[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link GetValueResult}.
+ */
+ public static final class Builder {
+ @ResultCode
+ private final int mResultCode;
+ private SettingsPreferenceValue mValue;
+ private SettingsPreferenceMetadata mMetadata;
+
+ /**
+ * Create Builder instance.
+ * @param resultCode indicates status of the request
+ */
+ public Builder(@ResultCode int resultCode) {
+ mResultCode = resultCode;
+ }
+
+ /**
+ * Sets the preference value on the result.
+ */
+ @NonNull
+ public Builder setValue(@Nullable SettingsPreferenceValue value) {
+ mValue = value;
+ return this;
+ }
+
+ /**
+ * Sets the metadata on the result.
+ */
+ @NonNull
+ public Builder setMetadata(@Nullable SettingsPreferenceMetadata metadata) {
+ mMetadata = metadata;
+ return this;
+ }
+
+ /**
+ * Constructs an immutable {@link GetValueResult} object.
+ */
+ @NonNull
+ public GetValueResult build() {
+ return new GetValueResult(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/IGetValueCallback.aidl b/core/java/android/service/settings/preferences/IGetValueCallback.aidl
new file mode 100644
index 000000000000..bbc7423f453e
--- /dev/null
+++ b/core/java/android/service/settings/preferences/IGetValueCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.GetValueResult;
+
+/** @hide */
+oneway interface IGetValueCallback {
+ void onSuccess(in GetValueResult result) = 1;
+ void onFailure() = 2;
+}
diff --git a/core/java/android/service/settings/preferences/IMetadataCallback.aidl b/core/java/android/service/settings/preferences/IMetadataCallback.aidl
new file mode 100644
index 000000000000..3bd5ebe93660
--- /dev/null
+++ b/core/java/android/service/settings/preferences/IMetadataCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.MetadataResult;
+
+/** @hide */
+oneway interface IMetadataCallback {
+ void onSuccess(in MetadataResult result);
+ void onFailure();
+}
diff --git a/core/java/android/service/settings/preferences/ISetValueCallback.aidl b/core/java/android/service/settings/preferences/ISetValueCallback.aidl
new file mode 100644
index 000000000000..0765660c83c3
--- /dev/null
+++ b/core/java/android/service/settings/preferences/ISetValueCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.SetValueResult;
+
+/** @hide */
+oneway interface ISetValueCallback {
+ void onSuccess(in SetValueResult result);
+ void onFailure();
+}
diff --git a/core/java/android/service/settings/preferences/ISettingsPreferenceService.aidl b/core/java/android/service/settings/preferences/ISettingsPreferenceService.aidl
new file mode 100644
index 000000000000..64a8b90fe581
--- /dev/null
+++ b/core/java/android/service/settings/preferences/ISettingsPreferenceService.aidl
@@ -0,0 +1,18 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.GetValueRequest;
+import android.service.settings.preferences.IGetValueCallback;
+import android.service.settings.preferences.IMetadataCallback;
+import android.service.settings.preferences.ISetValueCallback;
+import android.service.settings.preferences.MetadataRequest;
+import android.service.settings.preferences.SetValueRequest;
+
+/** @hide */
+oneway interface ISettingsPreferenceService {
+ @EnforcePermission("READ_SYSTEM_PREFERENCES")
+ void getAllPreferenceMetadata(in MetadataRequest request, IMetadataCallback callback) = 1;
+ @EnforcePermission("READ_SYSTEM_PREFERENCES")
+ void getPreferenceValue(in GetValueRequest request, IGetValueCallback callback) = 2;
+ @EnforcePermission(allOf = {"READ_SYSTEM_PREFERENCES", "WRITE_SYSTEM_PREFERENCES"})
+ void setPreferenceValue(in SetValueRequest request, ISetValueCallback callback) = 3;
+}
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.aidl b/core/java/android/service/settings/preferences/MetadataRequest.aidl
new file mode 100644
index 000000000000..dc3cbc42661e
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable MetadataRequest; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java
new file mode 100644
index 000000000000..ffecc6bec5b2
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataRequest.java
@@ -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 android.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+/**
+ * Request parameters to retrieve all metadata for all available settings preferences within this
+ * application.
+ *
+ * <p>This object passed to {@link SettingsPreferenceService#onGetAllPreferenceMetadata} will result
+ * in a {@link MetadataResult}.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class MetadataRequest implements Parcelable {
+ private MetadataRequest() {}
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link MetadataRequest}.
+ */
+ @NonNull
+ public static final Creator<MetadataRequest> CREATOR = new Creator<>() {
+ @Override
+ public MetadataRequest createFromParcel(@NonNull Parcel in) {
+ return new MetadataRequest();
+ }
+
+ @Override
+ public MetadataRequest[] newArray(int size) {
+ return new MetadataRequest[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link MetadataRequest}.
+ */
+ public static final class Builder {
+ /** Constructs an immutable {@link MetadataRequest} object. */
+ @NonNull
+ public MetadataRequest build() {
+ return new MetadataRequest();
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/MetadataResult.aidl b/core/java/android/service/settings/preferences/MetadataResult.aidl
new file mode 100644
index 000000000000..af9e8a86e3ab
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable MetadataResult; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java
new file mode 100644
index 000000000000..6a65dcc9c757
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataResult.java
@@ -0,0 +1,164 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Result object given a corresponding {@link MetadataRequest}.
+ * <ul>
+ * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK} and
+ * {@link #getMetadataList} will be populated with metadata for all available preferences within
+ * this application.
+ * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ * of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class MetadataResult implements Parcelable {
+
+ @ResultCode
+ private final int mResultCode;
+ @NonNull
+ private final List<SettingsPreferenceMetadata> mMetadataList;
+
+ /**
+ * Returns the result code indicating status of the request.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Returns the list of available Preference Metadata.
+ * <p>This instance is shared so this list should not be modified.
+ */
+ @NonNull
+ public List<SettingsPreferenceMetadata> getMetadataList() {
+ return mMetadataList;
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_OK,
+ RESULT_UNSUPPORTED,
+ RESULT_INTERNAL_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {
+ }
+
+ /** Request is successful. */
+ public static final int RESULT_OK = 0;
+ /**
+ * No preferences in this application support this API.
+ * <p>Retry not advised.
+ */
+ public static final int RESULT_UNSUPPORTED = 1;
+ /**
+ * API call failed due to an issue with the service binding.
+ * <p>Retry may succeed.
+ */
+ public static final int RESULT_INTERNAL_ERROR = 2;
+
+ private MetadataResult(@NonNull Builder builder) {
+ mResultCode = builder.mResultCode;
+ mMetadataList = builder.mMetadataList;
+ }
+ private MetadataResult(@NonNull Parcel in) {
+ mResultCode = in.readInt();
+ mMetadataList = new ArrayList<>();
+ in.readTypedList(mMetadataList, SettingsPreferenceMetadata.CREATOR);
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mResultCode);
+ dest.writeTypedList(mMetadataList, flags);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link MetadataResult}.
+ */
+ @NonNull
+ public static final Creator<MetadataResult> CREATOR = new Creator<>() {
+ @Override
+ public MetadataResult createFromParcel(@NonNull Parcel in) {
+ return new MetadataResult(in);
+ }
+
+ @Override
+ public MetadataResult[] newArray(int size) {
+ return new MetadataResult[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link MetadataResult}.
+ */
+ public static final class Builder {
+ @ResultCode
+ private final int mResultCode;
+ private List<SettingsPreferenceMetadata> mMetadataList = Collections.emptyList();
+
+ /**
+ * Create Builder instance.
+ * @param resultCode indicates status of the request
+ */
+ public Builder(@ResultCode int resultCode) {
+ mResultCode = resultCode;
+ }
+
+ /**
+ * Sets the metadata list on the result.
+ */
+ @NonNull
+ public Builder setMetadataList(@NonNull List<SettingsPreferenceMetadata> metadataList) {
+ mMetadataList = metadataList;
+ return this;
+ }
+
+ /**
+ * Constructs an immutable {@link MetadataResult} object.
+ */
+ @NonNull
+ public MetadataResult build() {
+ return new MetadataResult(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.aidl b/core/java/android/service/settings/preferences/SetValueRequest.aidl
new file mode 100644
index 000000000000..198e333d5cb6
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable SetValueRequest; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java
new file mode 100644
index 000000000000..f7600aecdfaf
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueRequest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Request parameters to set the current value to a Settings Preference.
+ * <p>This object passed to {@link SettingsPreferenceService#onSetPreferenceValue} will result in a
+ * {@link SetValueResult}.
+ * <ul>
+ * <li>{@link #getScreenKey} is a parameter to distinguish the container screen
+ * of a preference as a preference key may not be unique within its application.
+ * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is
+ * being requested. These keys will be unique with their Preference Screen, but may not be unique
+ * within their application, so it is required to pair this with {@link #getScreenKey} to
+ * ensure this request matches the intended target.
+ * <li>{@link #getPreferenceValue} is a parameter to specify the value that this request aims to
+ * set. If this value is invalid (malformed or does not match the type of the preference) then
+ * this request will fail.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SetValueRequest implements Parcelable {
+
+ @NonNull
+ private final String mScreenKey;
+ @NonNull
+ private final String mPreferenceKey;
+ @NonNull
+ private final SettingsPreferenceValue mPreferenceValue;
+
+ /**
+ * Returns the screen key of requested Preference.
+ */
+ @NonNull
+ public String getScreenKey() {
+ return mScreenKey;
+ }
+
+ /**
+ * Returns the key of requested Preference.
+ */
+ @NonNull
+ public String getPreferenceKey() {
+ return mPreferenceKey;
+ }
+
+ /**
+ * Returns the value of requested Preference.
+ */
+ @NonNull
+ public SettingsPreferenceValue getPreferenceValue() {
+ return mPreferenceValue;
+ }
+
+ private SetValueRequest(@NonNull Builder builder) {
+ mScreenKey = builder.mScreenKey;
+ mPreferenceKey = builder.mPreferenceKey;
+ mPreferenceValue = builder.mPreferenceValue;
+ }
+
+ private SetValueRequest(@NonNull Parcel in) {
+ mScreenKey = Objects.requireNonNull(in.readString8());
+ mPreferenceKey = Objects.requireNonNull(in.readString8());
+ mPreferenceValue = Objects.requireNonNull(in.readParcelable(
+ SettingsPreferenceValue.class.getClassLoader(), SettingsPreferenceValue.class));
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mScreenKey);
+ dest.writeString8(mPreferenceKey);
+ dest.writeParcelable(mPreferenceValue, flags);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link SetValueRequest}.
+ */
+ @NonNull
+ public static final Creator<SetValueRequest> CREATOR = new Creator<SetValueRequest>() {
+ @Override
+ public SetValueRequest createFromParcel(@NonNull Parcel in) {
+ return new SetValueRequest(in);
+ }
+
+ @Override
+ public SetValueRequest[] newArray(int size) {
+ return new SetValueRequest[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link SetValueRequest}.
+ */
+ public static final class Builder {
+ private final String mScreenKey;
+ private final String mPreferenceKey;
+ private final SettingsPreferenceValue mPreferenceValue;
+
+ /**
+ * Create Builder instance.
+ * @param screenKey required to be not empty
+ * @param preferenceKey required to be not empty
+ * @param value value to set to requested Preference
+ */
+ public Builder(@NonNull String screenKey, @NonNull String preferenceKey,
+ @NonNull SettingsPreferenceValue value) {
+ if (TextUtils.isEmpty(screenKey)) {
+ throw new IllegalArgumentException("screenKey cannot be empty");
+ }
+ if (TextUtils.isEmpty(preferenceKey)) {
+ throw new IllegalArgumentException("preferenceKey cannot be empty");
+ }
+ mScreenKey = screenKey;
+ mPreferenceKey = preferenceKey;
+ mPreferenceValue = value;
+ }
+
+ /**
+ * Constructs an immutable {@link SetValueRequest} object.
+ */
+ @NonNull
+ public SetValueRequest build() {
+ return new SetValueRequest(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/SetValueResult.aidl b/core/java/android/service/settings/preferences/SetValueResult.aidl
new file mode 100644
index 000000000000..f54813484d68
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable SetValueResult; \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java
new file mode 100644
index 000000000000..cb1776abd3bc
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueResult.java
@@ -0,0 +1,179 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result object given a corresponding {@link SetValueRequest}.
+ * <ul>
+ * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}.
+ * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ * of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SetValueResult implements Parcelable {
+
+ @ResultCode
+ private final int mResultCode;
+
+ /**
+ * Returns the result code indicating status of the request.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_OK,
+ RESULT_UNSUPPORTED,
+ RESULT_DISABLED,
+ RESULT_RESTRICTED,
+ RESULT_UNAVAILABLE,
+ RESULT_REQUIRE_APP_PERMISSION,
+ RESULT_REQUIRE_USER_CONSENT,
+ RESULT_DISALLOW,
+ RESULT_INVALID_REQUEST,
+ RESULT_INTERNAL_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {
+ }
+
+ /** Request is successful and the value was set. */
+ public static final int RESULT_OK = 0;
+ /**
+ * Requested preference is not supported by this API.
+ * <p>Retry not advised.
+ */
+ public static final int RESULT_UNSUPPORTED = 1;
+ /**
+ * Requested preference is disabled, thus unable to be set in this state.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_DISABLED = 2;
+ /**
+ * Requested preference is restricted, thus unable to be set under this policy.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_RESTRICTED = 3;
+ /**
+ * Preference is currently not available, likely due to device state or the state of
+ * a dependency.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_UNAVAILABLE = 4;
+ /**
+ * Requested preference requires permissions not held by the calling application.
+ * <p>Retry may succeed if necessary permissions are obtained.
+ */
+ public static final int RESULT_REQUIRE_APP_PERMISSION = 5;
+ /**
+ * User consent was not approved for this operation.
+ * <p>Retry may succeed if user provides consent.
+ */
+ public static final int RESULT_REQUIRE_USER_CONSENT = 6;
+ /**
+ * Requested preference is not allowed for access in this API under the current device policy.
+ * <p>Retry may succeed if underlying conditions change.
+ */
+ public static final int RESULT_DISALLOW = 7;
+ /**
+ * Request object is not valid.
+ * <p>Retry not advised with current parameters.
+ */
+ public static final int RESULT_INVALID_REQUEST = 8;
+ /**
+ * API call failed due to an issue with the service binding.
+ * <p>Retry may succeed.
+ */
+ public static final int RESULT_INTERNAL_ERROR = 9;
+
+ private SetValueResult(@NonNull Builder builder) {
+ mResultCode = builder.mResultCode;
+ }
+
+ private SetValueResult(@NonNull Parcel in) {
+ mResultCode = in.readInt();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mResultCode);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link SetValueResult}.
+ */
+ @NonNull
+ public static final Creator<SetValueResult> CREATOR = new Creator<>() {
+ @Override
+ public SetValueResult createFromParcel(@NonNull Parcel in) {
+ return new SetValueResult(in);
+ }
+
+ @Override
+ public SetValueResult[] newArray(int size) {
+ return new SetValueResult[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link SetValueResult}.
+ */
+ public static final class Builder {
+ @ResultCode
+ private final int mResultCode;
+
+ /**
+ * Create Builder instance.
+ * @param resultCode indicates status of the request
+ */
+ public Builder(@ResultCode int resultCode) {
+ mResultCode = resultCode;
+ }
+
+ /**
+ * Constructs an immutable {@link SetValueResult} object.
+ */
+ @NonNull
+ public SetValueResult build() {
+ return new SetValueResult(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
new file mode 100644
index 000000000000..1d08c5217129
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
@@ -0,0 +1,436 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Data object representation of a Settings Preference definition and state.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SettingsPreferenceMetadata implements Parcelable {
+
+ @NonNull
+ private final String mKey;
+ @NonNull
+ private final String mScreenKey;
+ @Nullable
+ private final String mTitle;
+ @Nullable
+ private final String mSummary;
+ @NonNull
+ private final List<String> mBreadcrumbs;
+ @NonNull
+ private final List<String> mReadPermissions;
+ @NonNull
+ private final List<String> mWritePermissions;
+ private final boolean mEnabled;
+ private final boolean mAvailable;
+ private final boolean mWritable;
+ private final boolean mRestricted;
+ private final int mSensitivity;
+ @Nullable
+ private final PendingIntent mLaunchIntent;
+ @NonNull
+ private final Bundle mExtras;
+
+ /**
+ * Returns the key of Preference.
+ */
+ @NonNull
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
+ * Returns the screen key of Preference.
+ */
+ @NonNull
+ public String getScreenKey() {
+ return mScreenKey;
+ }
+
+ /**
+ * Returns the title of Preference.
+ */
+ @Nullable
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the summary of Preference.
+ */
+ @Nullable
+ public String getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * Returns the breadcrumbs (navigation context) of Preference.
+ * <p>May be empty.
+ */
+ @NonNull
+ public List<String> getBreadcrumbs() {
+ return mBreadcrumbs;
+ }
+
+ /**
+ * Returns the permissions required to read this Preference's value.
+ * <p>May be empty.
+ */
+ @NonNull
+ public List<String> getReadPermissions() {
+ return mReadPermissions;
+ }
+
+ /**
+ * Returns the permissions required to write this Preference's value.
+ * <p>May be empty.
+ */
+ @NonNull
+ public List<String> getWritePermissions() {
+ return mWritePermissions;
+ }
+
+ /**
+ * Returns whether Preference is enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Returns whether Preference is available.
+ */
+ public boolean isAvailable() {
+ return mAvailable;
+ }
+
+ /**
+ * Returns whether Preference is writable.
+ */
+ public boolean isWritable() {
+ return mWritable;
+ }
+
+ /**
+ * Returns whether Preference is restricted.
+ */
+ public boolean isRestricted() {
+ return mRestricted;
+ }
+
+ /**
+ * Returns the write-level sensitivity of Preference.
+ */
+ @WriteSensitivity
+ public int getWriteSensitivity() {
+ return mSensitivity;
+ }
+
+ /**
+ * Returns the intent to launch the host app page for this Preference.
+ */
+ @Nullable
+ public PendingIntent getLaunchIntent() {
+ return mLaunchIntent;
+ }
+
+ /**
+ * Returns any additional fields specific to this preference.
+ * <p>Treat all data as optional.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /** @hide */
+ @IntDef(value = {
+ NOT_SENSITIVE,
+ SENSITIVE,
+ INTENT_ONLY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WriteSensitivity {}
+
+ /**
+ * Preference is not sensitive, thus its value is writable without explicit consent, assuming
+ * all necessary permissions are granted.
+ */
+ public static final int NOT_SENSITIVE = 0;
+ /**
+ * Preference is sensitive, meaning that in addition to necessary permissions, writing its value
+ * will also request explicit user consent.
+ */
+ public static final int SENSITIVE = 1;
+ /**
+ * Preference is not permitted for write-access via API and must be changed via Settings page.
+ */
+ public static final int INTENT_ONLY = 2;
+
+ private SettingsPreferenceMetadata(@NonNull Builder builder) {
+ mKey = builder.mKey;
+ mScreenKey = builder.mScreenKey;
+ mTitle = builder.mTitle;
+ mSummary = builder.mSummary;
+ mBreadcrumbs = builder.mBreadcrumbs;
+ mReadPermissions = builder.mReadPermissions;
+ mWritePermissions = builder.mWritePermissions;
+ mEnabled = builder.mEnabled;
+ mAvailable = builder.mAvailable;
+ mWritable = builder.mWritable;
+ mRestricted = builder.mRestricted;
+ mSensitivity = builder.mSensitivity;
+ mLaunchIntent = builder.mLaunchIntent;
+ mExtras = Objects.requireNonNullElseGet(builder.mExtras, Bundle::new);
+ }
+ @SuppressLint("ParcelClassLoader")
+ private SettingsPreferenceMetadata(@NonNull Parcel in) {
+ mKey = Objects.requireNonNull(in.readString8());
+ mScreenKey = Objects.requireNonNull(in.readString8());
+ mTitle = in.readString8();
+ mSummary = in.readString8();
+ mBreadcrumbs = new ArrayList<>();
+ in.readStringList(mBreadcrumbs);
+ mReadPermissions = new ArrayList<>();
+ in.readStringList(mReadPermissions);
+ mWritePermissions = new ArrayList<>();
+ in.readStringList(mWritePermissions);
+ mEnabled = in.readBoolean();
+ mAvailable = in.readBoolean();
+ mWritable = in.readBoolean();
+ mRestricted = in.readBoolean();
+ mSensitivity = in.readInt();
+ mLaunchIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+ PendingIntent.class);
+ mExtras = Objects.requireNonNullElseGet(in.readBundle(), Bundle::new);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mKey);
+ dest.writeString8(mScreenKey);
+ dest.writeString8(mTitle);
+ dest.writeString8(mSummary);
+ dest.writeStringList(mBreadcrumbs);
+ dest.writeStringList(mReadPermissions);
+ dest.writeStringList(mWritePermissions);
+ dest.writeBoolean(mEnabled);
+ dest.writeBoolean(mAvailable);
+ dest.writeBoolean(mWritable);
+ dest.writeBoolean(mRestricted);
+ dest.writeInt(mSensitivity);
+ dest.writeParcelable(mLaunchIntent, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Parcelable Creator for {@link SettingsPreferenceMetadata}.
+ */
+ @NonNull
+ public static final Creator<SettingsPreferenceMetadata> CREATOR = new Creator<>() {
+ @Override
+ public SettingsPreferenceMetadata createFromParcel(@NonNull Parcel in) {
+ return new SettingsPreferenceMetadata(in);
+ }
+
+ @Override
+ public SettingsPreferenceMetadata[] newArray(int size) {
+ return new SettingsPreferenceMetadata[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link SettingsPreferenceMetadata}.
+ */
+ public static final class Builder {
+ private final String mScreenKey;
+ private final String mKey;
+ private String mTitle;
+ private String mSummary;
+ private List<String> mBreadcrumbs = Collections.emptyList();
+ private List<String> mReadPermissions = Collections.emptyList();
+ private List<String> mWritePermissions = Collections.emptyList();
+ private boolean mEnabled = false;
+ private boolean mAvailable = false;
+ private boolean mWritable = false;
+ private boolean mRestricted = false;
+ @WriteSensitivity private int mSensitivity = INTENT_ONLY;
+ private PendingIntent mLaunchIntent;
+ private Bundle mExtras;
+
+ /**
+ * Create Builder instance.
+ * @param screenKey required to be not empty
+ * @param key required to be not empty
+ */
+ public Builder(@NonNull String screenKey, @NonNull String key) {
+ if (TextUtils.isEmpty(screenKey)) {
+ throw new IllegalArgumentException("screenKey cannot be empty");
+ }
+ if (TextUtils.isEmpty(key)) {
+ throw new IllegalArgumentException("key cannot be empty");
+ }
+ mScreenKey = screenKey;
+ mKey = key;
+ }
+
+ /**
+ * Sets the preference title.
+ */
+ @NonNull
+ public Builder setTitle(@Nullable String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the preference summary.
+ */
+ @NonNull
+ public Builder setSummary(@Nullable String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets the preference breadcrumbs (navigation context).
+ */
+ @NonNull
+ public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) {
+ mBreadcrumbs = breadcrumbs;
+ return this;
+ }
+
+ /**
+ * Sets the permissions required for reading this preference.
+ */
+ @NonNull
+ public Builder setReadPermissions(@NonNull List<String> readPermissions) {
+ mReadPermissions = readPermissions;
+ return this;
+ }
+
+ /**
+ * Sets the permissions required for writing this preference.
+ */
+ @NonNull
+ public Builder setWritePermissions(@NonNull List<String> writePermissions) {
+ mWritePermissions = writePermissions;
+ return this;
+ }
+
+ /**
+ * Set whether the preference is enabled.
+ */
+ @NonNull
+ public Builder setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ return this;
+ }
+
+ /**
+ * Sets whether the preference is available.
+ */
+ @NonNull
+ public Builder setAvailable(boolean available) {
+ mAvailable = available;
+ return this;
+ }
+
+ /**
+ * Sets whether the preference is writable.
+ */
+ @NonNull
+ public Builder setWritable(boolean writable) {
+ mWritable = writable;
+ return this;
+ }
+
+ /**
+ * Sets whether the preference is restricted.
+ */
+ @NonNull
+ public Builder setRestricted(boolean restricted) {
+ mRestricted = restricted;
+ return this;
+ }
+
+ /**
+ * Sets the preference write-level sensitivity.
+ */
+ @NonNull
+ public Builder setWriteSensitivity(@WriteSensitivity int sensitivity) {
+ mSensitivity = sensitivity;
+ return this;
+ }
+
+ /**
+ * Sets the intent to launch the host app page for this preference.
+ */
+ @NonNull
+ public Builder setLaunchIntent(@Nullable PendingIntent launchIntent) {
+ mLaunchIntent = launchIntent;
+ return this;
+ }
+
+ /**
+ * Sets additional fields specific to this preference. Treat all data as optional.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Constructs an immutable {@link SettingsPreferenceMetadata} object.
+ */
+ @NonNull
+ public SettingsPreferenceMetadata build() {
+ return new SettingsPreferenceMetadata(this);
+ }
+ }
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceService.java b/core/java/android/service/settings/preferences/SettingsPreferenceService.java
new file mode 100644
index 000000000000..4a4b5d201f09
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceService.java
@@ -0,0 +1,201 @@
+/*
+ * 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.service.settings.preferences;
+
+import android.Manifest;
+import android.annotation.EnforcePermission;
+import android.annotation.FlaggedApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.PermissionEnforcer;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+/**
+ * Base class for a service that exposes its settings preferences to external access.
+ * <p>This class is to be implemented by apps that contribute to the Android Settings surface.
+ * Access to this service is permission guarded by
+ * {@link android.permission.READ_SYSTEM_PREFERENCES} for binding and reading, and guarded by both
+ * {@link android.permission.READ_SYSTEM_PREFERENCES} and
+ * {@link android.permission.WRITE_SYSTEM_PREFERENCES} for writing. An additional checks for access
+ * control are the responsibility of the implementing class.
+ *
+ * <p>This implementation must correspond to an exported service declaration in the host app
+ * AndroidManifest.xml as follows
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ * android:permission="android.permission.READ_SYSTEM_PREFERENCES"
+ * android:exported="true">
+ * <intent-filter>
+ * <action android:name="android.service.settings.preferences.action.PREFERENCE_SERVICE" />
+ * </intent-filter>
+ * </service>}
+ * </pre>
+ *
+ * <ul>
+ * <li>It is recommended to expose the metadata for most, if not all, preferences within a
+ * settings app, thus implementing {@link #onGetAllPreferenceMetadata}.
+ * <li>Exposing preferences for read access of their values is up to the implementer, but any
+ * exposed must be a subset of the preferences exposed in {@link #onGetAllPreferenceMetadata}.
+ * To expose a preference for read access, the implementation will contain
+ * {@link #onGetPreferenceValue}.
+ * <li>Exposing a preference for write access of their values is up to the implementer, but should
+ * be done so with extra care and consideration, both for security and privacy. These must also
+ * be a subset of those exposed in {@link #onGetAllPreferenceMetadata}. To expose a preference for
+ * write access, the implementation will contain {@link #onSetPreferenceValue}.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public abstract class SettingsPreferenceService extends Service {
+
+ /**
+ * Intent Action corresponding to a {@link SettingsPreferenceService}. Note that any checks for
+ * such services must be accompanied by a check to ensure the host is a system application.
+ * Given an {@link android.content.pm.ApplicationInfo} you can check for
+ * {@link android.content.pm.ApplicationInfo#FLAG_SYSTEM}, or when querying
+ * {@link PackageManager#queryIntentServices} you can provide the flag
+ * {@link PackageManager#MATCH_SYSTEM_ONLY}.
+ */
+ public static final String ACTION_PREFERENCE_SERVICE =
+ "android.service.settings.preferences.action.PREFERENCE_SERVICE";
+
+ /** @hide */
+ @NonNull
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return new ISettingsPreferenceService.Stub(
+ PermissionEnforcer.fromContext(getApplicationContext())) {
+ @EnforcePermission(Manifest.permission.READ_SYSTEM_PREFERENCES)
+ @Override
+ public void getAllPreferenceMetadata(MetadataRequest request,
+ IMetadataCallback callback) {
+ getAllPreferenceMetadata_enforcePermission();
+ onGetAllPreferenceMetadata(request, new OutcomeReceiver<>() {
+ @Override
+ public void onResult(MetadataResult result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+
+ @EnforcePermission(Manifest.permission.READ_SYSTEM_PREFERENCES)
+ @Override
+ public void getPreferenceValue(GetValueRequest request, IGetValueCallback callback) {
+ getPreferenceValue_enforcePermission();
+ onGetPreferenceValue(request, new OutcomeReceiver<>() {
+ @Override
+ public void onResult(GetValueResult result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+
+ @EnforcePermission(allOf = {
+ Manifest.permission.READ_SYSTEM_PREFERENCES,
+ Manifest.permission.WRITE_SYSTEM_PREFERENCES
+ })
+ @Override
+ public void setPreferenceValue(SetValueRequest request, ISetValueCallback callback) {
+ setPreferenceValue_enforcePermission();
+ onSetPreferenceValue(request, new OutcomeReceiver<>() {
+ @Override
+ public void onResult(SetValueResult result) {
+ try {
+ callback.onSuccess(result);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ try {
+ callback.onFailure();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+ };
+ }
+
+ /**
+ * Retrieve the metadata for all exposed settings preferences within this application. This
+ * data should be a snapshot of their state at the time of this method being called.
+ * @param request object to specify request parameters
+ * @param callback object to receive result or failure of request
+ */
+ public abstract void onGetAllPreferenceMetadata(
+ @NonNull MetadataRequest request,
+ @NonNull OutcomeReceiver<MetadataResult, Exception> callback);
+
+ /**
+ * Retrieve the current value of the requested settings preference. If this value is not exposed
+ * or cannot be obtained for some reason, the corresponding result code will be set on the
+ * result object.
+ * @param request object to specify request parameters
+ * @param callback object to receive result or failure of request
+ */
+ public abstract void onGetPreferenceValue(
+ @NonNull GetValueRequest request,
+ @NonNull OutcomeReceiver<GetValueResult, Exception> callback);
+
+ /**
+ * Set the value within the request to the target settings preference. If this value cannot
+ * be written for some reason, the corresponding result code will be set on the result object.
+ * @param request object to specify request parameters
+ * @param callback object to receive result or failure of request
+ */
+ public abstract void onSetPreferenceValue(
+ @NonNull SetValueRequest request,
+ @NonNull OutcomeReceiver<SetValueResult, Exception> callback);
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceServiceClient.java b/core/java/android/service/settings/preferences/SettingsPreferenceServiceClient.java
new file mode 100644
index 000000000000..39995a47fcbe
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceServiceClient.java
@@ -0,0 +1,248 @@
+/*
+ * 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.service.settings.preferences;
+
+import static android.service.settings.preferences.SettingsPreferenceService.ACTION_PREFERENCE_SERVICE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Client class responsible for binding to and interacting with an instance of
+ * {@link SettingsPreferenceService}.
+ * <p>This is a convenience class to handle the lifecycle of the service connection.
+ * <p>This client will only interact with one instance at a time,
+ * so if the caller requires multiple instances (multiple applications that provide settings), then
+ * the caller must create multiple client classes, one for each instance required. To find all
+ * available services, a caller may query {@link android.content.pm.PackageManager} for applications
+ * that provide the intent action {@link SettingsPreferenceService#ACTION_PREFERENCE_SERVICE} that
+ * are also system applications ({@link android.content.pm.ApplicationInfo#FLAG_SYSTEM}).
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public class SettingsPreferenceServiceClient implements AutoCloseable {
+
+ private final Context mContext;
+ private final Intent mServiceIntent;
+ private final ServiceConnection mServiceConnection;
+ private final boolean mSystemOnly;
+ private ISettingsPreferenceService mRemoteService;
+
+ /**
+ * Construct a client for binding to a {@link SettingsPreferenceService} provided by the
+ * application corresponding to the provided package name.
+ * @param packageName - package name for which this client will initiate a service binding
+ */
+ public SettingsPreferenceServiceClient(@NonNull Context context,
+ @NonNull String packageName) {
+ this(context, packageName, true, null);
+ }
+
+ /**
+ * @hide Only to be called directly by test
+ */
+ @TestApi
+ public SettingsPreferenceServiceClient(@NonNull Context context,
+ @NonNull String packageName,
+ boolean systemOnly,
+ @Nullable ServiceConnection connectionListener) {
+ mContext = context.getApplicationContext();
+ mServiceIntent = new Intent(ACTION_PREFERENCE_SERVICE).setPackage(packageName);
+ mSystemOnly = systemOnly;
+ mServiceConnection = createServiceConnection(connectionListener);
+ }
+
+ /**
+ * Initiate binding to service.
+ * <p>If no service exists for the package provided or the package is not for a system
+ * application, no binding will occur.
+ */
+ public void start() {
+ PackageManager pm = mContext.getPackageManager();
+ PackageManager.ResolveInfoFlags flags;
+ if (mSystemOnly) {
+ flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY);
+ } else {
+ flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL);
+ }
+ List<ResolveInfo> infos = pm.queryIntentServices(mServiceIntent, flags);
+ if (infos.size() == 1) {
+ mContext.bindService(mServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ }
+
+ /**
+ * If there is an active service binding, unbind from that service.
+ */
+ public void stop() {
+ if (mRemoteService != null) {
+ mRemoteService = null;
+ mContext.unbindService(mServiceConnection);
+ }
+ }
+
+ /**
+ * Retrieve the metadata for all exposed settings preferences within the application.
+ * @param request object to specify request parameters
+ * @param executor {@link Executor} on which to invoke the receiver
+ * @param receiver callback to receive the result or failure
+ */
+ public void getAllPreferenceMetadata(
+ @NonNull MetadataRequest request,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<MetadataResult, Exception> receiver) {
+ if (mRemoteService == null) {
+ executor.execute(() ->
+ receiver.onError(new IllegalStateException("Service not ready")));
+ return;
+ }
+ try {
+ mRemoteService.getAllPreferenceMetadata(request, new IMetadataCallback.Stub() {
+ @Override
+ public void onSuccess(MetadataResult result) {
+ executor.execute(() -> receiver.onResult(result));
+ }
+
+ @Override
+ public void onFailure() {
+ executor.execute(() -> receiver.onError(
+ new IllegalStateException("Service call failure")));
+ }
+ });
+ } catch (RemoteException | RuntimeException e) {
+ executor.execute(() -> receiver.onError(e));
+ }
+ }
+
+ /**
+ * Retrieve the current value of the requested settings preference.
+ * @param request object to specify request parameters
+ * @param executor {@link Executor} on which to invoke the receiver
+ * @param receiver callback to receive the result or failure
+ */
+ public void getPreferenceValue(@NonNull GetValueRequest request,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<GetValueResult, Exception> receiver) {
+ if (mRemoteService == null) {
+ executor.execute(() ->
+ receiver.onError(new IllegalStateException("Service not ready")));
+ return;
+ }
+ try {
+ mRemoteService.getPreferenceValue(request, new IGetValueCallback.Stub() {
+ @Override
+ public void onSuccess(GetValueResult result) {
+ executor.execute(() -> receiver.onResult(result));
+ }
+
+ @Override
+ public void onFailure() {
+ executor.execute(() -> receiver.onError(
+ new IllegalStateException("Service call failure")));
+ }
+ });
+ } catch (RemoteException | RuntimeException e) {
+ executor.execute(() -> receiver.onError(e));
+ }
+ }
+
+ /**
+ * Set the value on the target settings preference.
+ * @param request object to specify request parameters
+ * @param executor {@link Executor} on which to invoke the receiver
+ * @param receiver callback to receive the result or failure
+ */
+ public void setPreferenceValue(@NonNull SetValueRequest request,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<SetValueResult, Exception> receiver) {
+ if (mRemoteService == null) {
+ executor.execute(() ->
+ receiver.onError(new IllegalStateException("Service not ready")));
+ return;
+ }
+ try {
+ mRemoteService.setPreferenceValue(request, new ISetValueCallback.Stub() {
+ @Override
+ public void onSuccess(SetValueResult result) {
+ executor.execute(() -> receiver.onResult(result));
+ }
+
+ @Override
+ public void onFailure() {
+ executor.execute(() -> receiver.onError(
+ new IllegalStateException("Service call failure")));
+ }
+ });
+ } catch (RemoteException | RuntimeException e) {
+ executor.execute(() -> receiver.onError(e));
+ }
+ }
+
+ @NonNull
+ private ServiceConnection createServiceConnection(@Nullable ServiceConnection listener) {
+ return new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mRemoteService = getPreferenceServiceInterface(service);
+ if (listener != null) {
+ listener.onServiceConnected(name, service);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mRemoteService = null;
+ if (listener != null) {
+ listener.onServiceDisconnected(name);
+ }
+ }
+ };
+ }
+
+ @NonNull
+ private ISettingsPreferenceService getPreferenceServiceInterface(@NonNull IBinder service) {
+ return ISettingsPreferenceService.Stub.asInterface(service);
+ }
+
+ /**
+ * This client handles a resource, thus is it important to appropriately close that resource
+ * when it is no longer needed.
+ * <p>This method is provided by {@link AutoCloseable} and calling it
+ * will unbind any service binding.
+ */
+ @Override
+ public void close() {
+ stop();
+ }
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
new file mode 100644
index 000000000000..f056e34a0dd2
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This objects represents a value that can be used for a particular settings preference.
+ * <p>The data type for the value will correspond to {@link #getType}. For possible types, see
+ * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}.
+ * Depending on the type, the corresponding getter will contain its value. All other getters will
+ * return default values (boolean returns false, String returns null) so they should not be used.
+ * <p>See documentation on the constants for which getter method should be used.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SettingsPreferenceValue implements Parcelable {
+
+ @Type
+ private final int mType;
+ private final boolean mBooleanValue;
+ private final long mLongValue;
+ private final double mDoubleValue;
+ @Nullable
+ private final String mStringValue;
+
+ /**
+ * Returns the type indicator for Preference value.
+ */
+ @Type
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}.
+ */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns the long value for Preference if type is {@link #TYPE_LONG}.
+ */
+ public long getLongValue() {
+ return mLongValue;
+ }
+
+ /**
+ * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}.
+ */
+ public double getDoubleValue() {
+ return mDoubleValue;
+ }
+
+ /**
+ * Returns the string value for Preference if type is {@link #TYPE_STRING}.
+ */
+ @Nullable
+ public String getStringValue() {
+ return mStringValue;
+ }
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_BOOLEAN,
+ TYPE_LONG,
+ TYPE_DOUBLE,
+ TYPE_STRING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /** Value is of type boolean. Access via {@link #getBooleanValue}. */
+ public static final int TYPE_BOOLEAN = 0;
+ /** Value is of type long. Access via {@link #getLongValue()}. */
+ public static final int TYPE_LONG = 1;
+ /** Value is of type double. Access via {@link #getDoubleValue()}. */
+ public static final int TYPE_DOUBLE = 2;
+ /** Value is of type string. Access via {@link #getStringValue}. */
+ public static final int TYPE_STRING = 3;
+
+ private SettingsPreferenceValue(@NonNull Builder builder) {
+ mType = builder.mType;
+ mBooleanValue = builder.mBooleanValue;
+ mLongValue = builder.mLongValue;
+ mDoubleValue = builder.mDoubleValue;
+ mStringValue = builder.mStringValue;
+ }
+
+ private SettingsPreferenceValue(@NonNull Parcel in) {
+ mType = in.readInt();
+ mBooleanValue = in.readBoolean();
+ mLongValue = in.readLong();
+ mDoubleValue = in.readDouble();
+ mStringValue = in.readString8();
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeLong(mLongValue);
+ dest.writeDouble(mDoubleValue);
+ dest.writeString8(mStringValue);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable Creator for {@link SettingsPreferenceValue}.
+ */
+ @NonNull
+ public static final Creator<SettingsPreferenceValue> CREATOR = new Creator<>() {
+ @Override
+ public SettingsPreferenceValue createFromParcel(@NonNull Parcel in) {
+ return new SettingsPreferenceValue(in);
+ }
+
+ @Override
+ public SettingsPreferenceValue[] newArray(int size) {
+ return new SettingsPreferenceValue[size];
+ }
+ };
+
+ /**
+ * Builder to construct {@link SettingsPreferenceValue}.
+ */
+ public static final class Builder {
+ @Type
+ private final int mType;
+ private boolean mBooleanValue;
+ private long mLongValue;
+ private double mDoubleValue;
+ private String mStringValue;
+
+ /**
+ * Create Builder instance.
+ * @param type type indicator for preference value
+ */
+ public Builder(@Type int type) {
+ mType = type;
+ }
+
+ /**
+ * Sets boolean value for Preference.
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets long value for Preference.
+ */
+ @NonNull
+ public Builder setLongValue(long longValue) {
+ mLongValue = longValue;
+ return this;
+ }
+
+ /**
+ * Sets floating point value for Preference.
+ */
+ @NonNull
+ public Builder setDoubleValue(double doubleValue) {
+ mDoubleValue = doubleValue;
+ return this;
+ }
+
+ /**
+ * Sets string value for Preference.
+ */
+ @NonNull
+ public Builder setStringValue(@Nullable String stringValue) {
+ mStringValue = stringValue;
+ return this;
+ }
+
+ /**
+ * Constructs an immutable {@link SettingsPreferenceValue} object.
+ */
+ @NonNull
+ public SettingsPreferenceValue build() {
+ return new SettingsPreferenceValue(this);
+ }
+ }
+}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bce51f297aff..1df3b4332754 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -37,6 +37,7 @@ import android.telephony.TelephonyManager.EmergencyCallbackModeType;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -1706,6 +1707,11 @@ public class PhoneStateListener {
@NetworkRegistrationInfo.ServiceType int[] availableServices) {
// not supported on the deprecated interface - Use TelephonyCallback instead
}
+
+ public final void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 64a5533cbe69..0d1dc4611343 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -30,6 +30,7 @@ import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
import android.telephony.ims.MediaThreshold;
+import android.telephony.satellite.NtnSignalStrength;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -695,6 +696,15 @@ public class TelephonyCallback {
public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
/**
+ * Event for listening to carrier roaming non-terrestrial network signal strength changes.
+ *
+ * @see CarrierRoamingNtnModeListener
+ *
+ * @hide
+ */
+ public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -741,7 +751,8 @@ public class TelephonyCallback {
EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED,
- EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED
+ EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED,
+ EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1805,6 +1816,14 @@ public class TelephonyCallback {
*/
default void onCarrierRoamingNtnAvailableServicesChanged(
@NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
+
+ /**
+ * Callback invoked when carrier roaming non-terrestrial network signal strength changes.
+ *
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ */
+ default void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {}
}
/**
@@ -2270,5 +2289,18 @@ public class TelephonyCallback {
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
}
+
+ public void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!Flags.carrierRoamingNbIotNtn()) return;
+
+ CarrierRoamingNtnModeListener listener =
+ (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength)));
+
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 1dab2cf75594..90b0bb34c145 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,7 @@ import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteStateChangeListener;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1137,6 +1138,23 @@ public class TelephonyRegistryManager {
}
/**
+ * Notify external listeners that carrier roaming non-terrestrial network
+ * signal strength changed.
+ * @param subId subscription ID.
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ * @hide
+ */
+ public final void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ try {
+ sRegistry.notifyCarrierRoamingNtnSignalStrengthChanged(subId, ntnSignalStrength);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Processes potential event changes from the provided {@link TelephonyCallback}.
*
* @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1293,6 +1311,7 @@ public class TelephonyRegistryManager {
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED);
+ eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED);
}
return eventList;
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8f112f338a00..4ff04d5c1fa6 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -447,7 +447,6 @@ public final class DisplayInfo implements Parcelable {
&& Objects.equals(displayCutout, other.displayCutout)
&& rotation == other.rotation
&& modeId == other.modeId
- && renderFrameRate == other.renderFrameRate
&& hasArrSupport == other.hasArrSupport
&& Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
&& defaultModeId == other.defaultModeId
@@ -705,6 +704,9 @@ public final class DisplayInfo implements Parcelable {
if (refreshRateOverride > 0) {
return refreshRateOverride;
}
+ if (renderFrameRate > 0) {
+ return renderFrameRate;
+ }
if (supportedModes.length == 0) {
return 0;
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index dddc408ed9db..38e4e2760d25 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -935,7 +935,6 @@ public class KeyEvent extends InputEvent implements Parcelable {
*/
public static final int KEYCODE_MACRO_4 = 316;
/** Key code constant: To open emoji picker */
- @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
public static final int KEYCODE_EMOJI_PICKER = 317;
/**
* Key code constant: To take a screenshot
@@ -944,15 +943,80 @@ public class KeyEvent extends InputEvent implements Parcelable {
* unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app
* doesn't handle it, the framework handles it (to take a screenshot).
*/
- @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
public static final int KEYCODE_SCREENSHOT = 318;
+ /** Key code constant: To start dictate to an input field */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_DICTATE = 319;
+ /**
+ * Key code constant: AC New
+ *
+ * e.g. To create a new instance of a window, open a new tab, etc.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_NEW = 320;
+ /**
+ * Key code constant: AC Close
+ *
+ * e.g. To close current instance of the application window, close the current tab, etc.
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_CLOSE = 321;
+ /** Key code constant: To toggle 'Do Not Disturb' mode */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_DO_NOT_DISTURB = 322;
+ /** Key code constant: To Print */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_PRINT = 323;
+ /** Key code constant: To Lock the screen */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_LOCK = 324;
+ /** Key code constant: To toggle fullscreen mode (on the current application) */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_FULLSCREEN = 325;
+ /** Key code constant: F13 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F13 = 326;
+ /** Key code constant: F14 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F14 = 327;
+ /** Key code constant: F15 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F15 = 328;
+ /** Key code constant: F16 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F16 = 329;
+ /** Key code constant: F17 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F17 = 330;
+ /** Key code constant: F18 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F18 = 331;
+ /** Key code constant: F19 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F19 = 332;
+ /** Key code constant: F20 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F20 = 333;
+ /** Key code constant: F21 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F21 = 334;
+ /** Key code constant: F22 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F22 = 335;
+ /** Key code constant: F23 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F23 = 336;
+ /** Key code constant: F24 key. */
+ @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+ public static final int KEYCODE_F24 = 337;
/**
* Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
* @hide
*/
@TestApi
- public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;
+ @SuppressWarnings("FlaggedApi")
+ public static final int LAST_KEYCODE = KEYCODE_F24;
/** @hide */
@IntDef(prefix = {"KEYCODE_"}, value = {
@@ -1275,6 +1339,25 @@ public class KeyEvent extends InputEvent implements Parcelable {
KEYCODE_MACRO_4,
KEYCODE_EMOJI_PICKER,
KEYCODE_SCREENSHOT,
+ KEYCODE_DICTATE,
+ KEYCODE_NEW,
+ KEYCODE_CLOSE,
+ KEYCODE_DO_NOT_DISTURB,
+ KEYCODE_PRINT,
+ KEYCODE_LOCK,
+ KEYCODE_FULLSCREEN,
+ KEYCODE_F13,
+ KEYCODE_F14,
+ KEYCODE_F15,
+ KEYCODE_F16,
+ KEYCODE_F17,
+ KEYCODE_F18,
+ KEYCODE_F19,
+ KEYCODE_F20,
+ KEYCODE_F21,
+ KEYCODE_F22,
+ KEYCODE_F23,
+ KEYCODE_F24,
})
@Retention(RetentionPolicy.SOURCE)
@interface KeyCode {}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index df54d310059f..206c73756088 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2989,7 +2989,6 @@ public final class SurfaceControl implements Parcelable {
private void apply(boolean sync, boolean oneWay) {
applyResizedSurfaces();
notifyReparentedSurfaces();
- nativeApplyTransaction(mNativeObject, sync, oneWay);
if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
@@ -2998,6 +2997,7 @@ public final class SurfaceControl implements Parcelable {
if (mCalls != null) {
mCalls.clear();
}
+ nativeApplyTransaction(mNativeObject, sync, oneWay);
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 618843ccb005..fa06831f6514 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23882,12 +23882,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
draw(canvas);
}
- }
- // For VRR to vote the preferred frame rate
- if (sToolkitSetFrameRateReadOnlyFlagValue
- && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
- votePreferredFrameRate();
+ // For VRR to vote the preferred frame rate
+ if (sToolkitSetFrameRateReadOnlyFlagValue
+ && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+ votePreferredFrameRate();
+ }
}
} finally {
renderNode.endRecording();
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 14652035438d..0204517e869a 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3866,8 +3866,14 @@ public class AccessibilityNodeInfo implements Parcelable {
* Sets the view for which the view represented by this info serves as a
* label for accessibility purposes.
*
+ * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+ * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+ * labeled node are not automatically populated when this method is used.
+ *
* @param labeled The view for which this info serves as a label.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public void setLabelFor(View labeled) {
setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
}
@@ -3888,9 +3894,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
*
+ * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+ * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+ * labeled node are not automatically populated when this method is used.
+ *
* @param root The root whose virtual descendant serves as a label.
* @param virtualDescendantId The id of the virtual descendant.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public void setLabelFor(View root, int virtualDescendantId) {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
@@ -3902,8 +3914,14 @@ public class AccessibilityNodeInfo implements Parcelable {
* Gets the node info for which the view represented by this info serves as
* a label for accessibility purposes.
*
+ * @deprecated Use {@link #getLabeledByList()} on the labeled node instead,
+ * since calling {@link #addLabeledBy(View)} or {@link #addLabeledBy(View, int)}
+ * on the labeled node do not automatically provide that node from this method.
+ *
* @return The labeled info.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public AccessibilityNodeInfo getLabelFor() {
enforceSealed();
return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabelForId);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 7177ef330f06..8a006fa5b509 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -92,6 +92,13 @@ flag {
flag {
namespace: "accessibility"
+ name: "deprecate_ani_label_for_apis"
+ description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
+ bug: "333783827"
+}
+
+flag {
+ namespace: "accessibility"
name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index aa4927ee9b9c..edd9d6cff799 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -158,3 +158,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "writing_tools"
+ namespace: "input_method"
+ description: "Writing tools API"
+ bug: "373788889"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index dae87ddcb1bd..6b5a367ab460 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -75,7 +75,8 @@ public enum DesktopModeFlags {
Flags::enableDesktopAppLaunchAlttabTransitions, false),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
Flags::enableDesktopAppLaunchTransitions, false),
- ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false);
+ ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 9845bf00d937..68e78fed29c5 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -76,6 +76,14 @@ flag {
}
flag {
+ name: "disable_opt_out_edge_to_edge"
+ namespace: "windowing_frontend"
+ description: "Deprecate and disable windowOptOutEdgeToEdgeEnforcement"
+ bug: "377864165"
+ is_fixed_read_only: true
+}
+
+flag {
name: "keyguard_going_away_timeout"
namespace: "windowing_frontend"
description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting"
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index b3480ab92f46..2931bd2c83dd 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -56,7 +56,7 @@ import libcore.util.NativeAllocationRegistry;
* @hide
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
+@RavenwoodRedirectionClass("LongArrayMultiStateCounter_ravenwood")
public final class LongArrayMultiStateCounter implements Parcelable {
private static volatile NativeAllocationRegistry sRegistry;
private final int mStateCount;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
index 90608f6e87d1..7030d8e84b70 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
@@ -18,6 +18,7 @@ package com.android.internal.os;
import android.os.BadParcelableException;
import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import java.util.Arrays;
import java.util.HashMap;
@@ -25,7 +26,8 @@ import java.util.HashMap;
/**
* Native implementation substitutions for the LongArrayMultiStateCounter class.
*/
-public class LongArrayMultiStateCounter_host {
+@RavenwoodKeepWholeClass
+class LongArrayMultiStateCounter_ravenwood {
/**
* A reimplementation of {@link LongArrayMultiStateCounter}, only in
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
index c386a86f5906..ee855d58c874 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -60,7 +60,7 @@ import libcore.util.NativeAllocationRegistry;
* @hide
*/
@RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("LongMultiStateCounter_host")
+@RavenwoodRedirectionClass("LongMultiStateCounter_ravenwood")
public final class LongMultiStateCounter implements Parcelable {
private static NativeAllocationRegistry sRegistry;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java
index 1d95aa143549..42db37f1f82f 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java
@@ -18,13 +18,15 @@ package com.android.internal.os;
import android.os.BadParcelableException;
import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import java.util.HashMap;
/**
* Native implementation substitutions for the LongMultiStateCounter class.
*/
-public class LongMultiStateCounter_host {
+@RavenwoodKeepWholeClass
+class LongMultiStateCounter_ravenwood {
/**
* A reimplementation of {@link com.android.internal.os.LongMultiStateCounter}, only in
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index b5c87868af12..0e85e046e1b6 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -27,6 +27,7 @@ import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -85,4 +86,5 @@ oneway interface IPhoneStateListener {
void onCarrierRoamingNtnModeChanged(in boolean active);
void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices);
+ void onCarrierRoamingNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 1c76a6cd4bba..0f268d5de62b 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -29,6 +29,7 @@ import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -125,8 +126,10 @@ interface ITelephonyRegistry {
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+ void notifyCarrierRoamingNtnSignalStrengthChanged(int subId, in NtnSignalStrength ntnSignalStrength);
void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
void notifySatelliteStateChanged(boolean isEnabled);
+
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 30deb499594c..fb6937c94a3e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -63,6 +63,7 @@ public final class NotificationProgressDrawable extends Drawable {
private final ArrayList<Part> mParts = new ArrayList<>();
+ private final RectF mSegRectF = new RectF();
private final Rect mPointRect = new Rect();
private final RectF mPointRectF = new RectF();
@@ -198,22 +199,42 @@ public final class NotificationProgressDrawable extends Drawable {
mState.mSegSegGap, x + segWidth, totalWidth);
final float end = x + segWidth - endOffset;
- // Transparent is not allowed (and also is the default in the data), so use that
- // as a sentinel to be replaced by default
- mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : mState.mStrokeColor);
- mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : mState.mFadedStrokeColor);
-
- // Leave space for the rounded line cap which extends beyond start/end.
- final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
-
- canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
- segment.mDashed ? mDashedStrokePaint : mStrokePaint);
-
// Advance the current position to account for the segment's fraction of the total
// width (ignoring offset and padding)
x += segWidth;
+
+ // No space left to draw the segment
+ if (start > end) continue;
+
+ if (segment.mDashed) {
+ // No caps when the segment is dashed.
+
+ mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mFadedStrokeColor);
+ canvas.drawLine(start, centerY, end, centerY, mDashedStrokePaint);
+ } else if (end - start < mState.mStrokeWidth) {
+ // Not enough segment length to draw the caps
+
+ final float rad = (end - start) / 2F;
+ final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+ mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mStrokeColor);
+
+ mSegRectF.set(start, centerY - capWidth, end, centerY + capWidth);
+ canvas.drawRoundRect(mSegRectF, rad, rad, mFillPaint);
+ } else {
+ // Leave space for the rounded line cap which extends beyond start/end.
+ final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+ // Transparent is not allowed (and also is the default in the data), so use that
+ // as a sentinel to be replaced by default
+ mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mStrokeColor);
+
+ canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
+ mStrokePaint);
+ }
} else if (part instanceof Point point) {
final float pointWidth = 2 * pointRadius;
float start = x - pointRadius;
@@ -232,7 +253,7 @@ public final class NotificationProgressDrawable extends Drawable {
} else {
// TODO: b/367804171 - actually use a vector asset for the default point
// rather than drawing it as a box?
- mPointRectF.set(mPointRect);
+ mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
final float inset = mState.mPointRectInset;
final float cornerRadius = mState.mPointRectCornerRadius;
mPointRectF.inset(inset, inset);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 69f633420a0d..f1c4913fe006 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -83,68 +83,53 @@ static struct {
jmethodID ctor;
} gRegionClassInfo;
-static Mutex gHandleMutex;
-
-
-// --- NativeInputWindowHandle ---
-
-NativeInputWindowHandle::NativeInputWindowHandle(jweak objWeak) :
- mObjWeak(objWeak) {
-}
-
-NativeInputWindowHandle::~NativeInputWindowHandle() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mObjWeak);
+// --- Global functions ---
- // Clear the weak reference to the layer handle and flush any binder ref count operations so we
- // do not hold on to any binder references.
- // TODO(b/139697085) remove this after it can be flushed automatically
- mInfo.touchableRegionCropHandle.clear();
- IPCThreadState::self()->flushCommands();
-}
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env, jobject obj) {
+ sp<gui::WindowInfoHandle> handle = [&]() {
+ jlong cachedHandle = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
+ if (cachedHandle) {
+ return sp<gui::WindowInfoHandle>::fromExisting(
+ reinterpret_cast<gui::WindowInfoHandle*>(cachedHandle));
+ }
-jobject NativeInputWindowHandle::getInputWindowHandleObjLocalRef(JNIEnv* env) {
- return env->NewLocalRef(mObjWeak);
-}
+ auto newHandle = sp<gui::WindowInfoHandle>::make();
+ newHandle->incStrong((void*)android_view_InputWindowHandle_getHandle);
+ env->SetLongField(obj, gInputWindowHandleClassInfo.ptr,
+ reinterpret_cast<jlong>(newHandle.get()));
+ return newHandle;
+ }();
-bool NativeInputWindowHandle::updateInfo() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jobject obj = env->NewLocalRef(mObjWeak);
- if (!obj) {
- releaseChannel();
- return false;
- }
+ gui::WindowInfo* windowInfo = handle->editInfo();
- mInfo.touchableRegion.clear();
+ windowInfo->touchableRegion.clear();
jobject tokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.token);
if (tokenObj) {
- mInfo.token = ibinderForJavaObject(env, tokenObj);
+ windowInfo->token = ibinderForJavaObject(env, tokenObj);
env->DeleteLocalRef(tokenObj);
} else {
- mInfo.token.clear();
+ windowInfo->token.clear();
}
- mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
+ windowInfo->name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
- mInfo.dispatchingTimeout = std::chrono::milliseconds(
+ windowInfo->dispatchingTimeout = std::chrono::milliseconds(
env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
ScopedLocalRef<jobject> frameObj(env,
env->GetObjectField(obj, gInputWindowHandleClassInfo.frame));
- mInfo.frame = JNICommon::rectFromObj(env, frameObj.get());
+ windowInfo->frame = JNICommon::rectFromObj(env, frameObj.get());
- mInfo.surfaceInset = env->GetIntField(obj,
- gInputWindowHandleClassInfo.surfaceInset);
- mInfo.globalScaleFactor = env->GetFloatField(obj,
- gInputWindowHandleClassInfo.scaleFactor);
+ windowInfo->surfaceInset = env->GetIntField(obj, gInputWindowHandleClassInfo.surfaceInset);
+ windowInfo->globalScaleFactor =
+ env->GetFloatField(obj, gInputWindowHandleClassInfo.scaleFactor);
- jobject regionObj = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.touchableRegion);
+ jobject regionObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.touchableRegion);
if (regionObj) {
for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
ARect rect = it.getRect();
- mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
+ windowInfo->addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
}
env->DeleteLocalRef(regionObj);
}
@@ -153,49 +138,55 @@ bool NativeInputWindowHandle::updateInfo() {
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
const auto type = static_cast<WindowInfo::Type>(
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
- mInfo.layoutParamsFlags = flags;
- mInfo.layoutParamsType = type;
+ windowInfo->layoutParamsFlags = flags;
+ windowInfo->layoutParamsType = type;
- mInfo.inputConfig = static_cast<gui::WindowInfo::InputConfig>(
+ windowInfo->inputConfig = static_cast<gui::WindowInfo::InputConfig>(
env->GetIntField(obj, gInputWindowHandleClassInfo.inputConfig));
- mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
+ windowInfo->touchOcclusionMode = static_cast<TouchOcclusionMode>(
env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
- mInfo.ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
- mInfo.ownerUid = gui::Uid{
+ windowInfo->ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
+ windowInfo->ownerUid = gui::Uid{
static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
- mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
- mInfo.displayId =
+ windowInfo->packageName =
+ getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
+ windowInfo->displayId =
ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
- jobject inputApplicationHandleObj = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.inputApplicationHandle);
+ jobject inputApplicationHandleObj =
+ env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle);
if (inputApplicationHandleObj) {
std::shared_ptr<InputApplicationHandle> inputApplicationHandle =
android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
if (inputApplicationHandle != nullptr) {
inputApplicationHandle->updateInfo();
- mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
+ windowInfo->applicationInfo = *(inputApplicationHandle->getInfo());
}
env->DeleteLocalRef(inputApplicationHandleObj);
}
- mInfo.replaceTouchableRegionWithCrop = env->GetBooleanField(obj,
- gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
+ windowInfo->replaceTouchableRegionWithCrop =
+ env->GetBooleanField(obj, gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
- jobject weakSurfaceCtrl = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
+ jobject weakSurfaceCtrl =
+ env->GetObjectField(obj,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
bool touchableRegionCropHandleSet = false;
if (weakSurfaceCtrl) {
// Promote java weak reference.
- jobject strongSurfaceCtrl = env->CallObjectMethod(weakSurfaceCtrl,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.get);
+ jobject strongSurfaceCtrl =
+ env->CallObjectMethod(weakSurfaceCtrl,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+ .get);
if (strongSurfaceCtrl) {
- jlong mNativeObject = env->GetLongField(strongSurfaceCtrl,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject);
+ jlong mNativeObject =
+ env->GetLongField(strongSurfaceCtrl,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+ .mNativeObject);
if (mNativeObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(mNativeObject);
- mInfo.touchableRegionCropHandle = ctrl->getHandle();
+ windowInfo->touchableRegionCropHandle = ctrl->getHandle();
touchableRegionCropHandleSet = true;
}
env->DeleteLocalRef(strongSurfaceCtrl);
@@ -203,15 +194,15 @@ bool NativeInputWindowHandle::updateInfo() {
env->DeleteLocalRef(weakSurfaceCtrl);
}
if (!touchableRegionCropHandleSet) {
- mInfo.touchableRegionCropHandle.clear();
+ windowInfo->touchableRegionCropHandle.clear();
}
jobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken);
if (windowTokenObj) {
- mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj);
+ windowInfo->windowToken = ibinderForJavaObject(env, windowTokenObj);
env->DeleteLocalRef(windowTokenObj);
} else {
- mInfo.windowToken.clear();
+ windowInfo->windowToken.clear();
}
ScopedLocalRef<jobject>
@@ -220,41 +211,16 @@ bool NativeInputWindowHandle::updateInfo() {
gInputWindowHandleClassInfo
.focusTransferTarget));
if (focusTransferTargetObj.get()) {
- mInfo.focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
+ windowInfo->focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
} else {
- mInfo.focusTransferTarget.clear();
+ windowInfo->focusTransferTarget.clear();
}
- env->DeleteLocalRef(obj);
- return true;
-}
-
-
-// --- Global functions ---
-
-sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
- JNIEnv* env, jobject inputWindowHandleObj) {
- if (!inputWindowHandleObj) {
- return NULL;
- }
-
- AutoMutex _l(gHandleMutex);
-
- jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
- NativeInputWindowHandle* handle;
- if (ptr) {
- handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- } else {
- jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
- handle = new NativeInputWindowHandle(objWeak);
- handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
- env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
- reinterpret_cast<jlong>(handle));
- }
return handle;
}
-jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowInfo windowInfo) {
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+ const gui::WindowInfo& windowInfo) {
ScopedLocalRef<jobject>
applicationHandle(env,
android_view_InputApplicationHandle_fromInputApplicationInfo(
@@ -337,18 +303,15 @@ jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowIn
// --- JNI ---
static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
- AutoMutex _l(gHandleMutex);
-
jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
- if (ptr) {
- env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
-
- NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
+ if (!ptr) {
+ return;
}
+ env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
+ auto handle = reinterpret_cast<gui::WindowInfoHandle*>(ptr);
+ handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
}
-
static const JNINativeMethod gInputWindowHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 408e0f1bfa36..aa375e9ef477 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -24,24 +24,11 @@
namespace android {
-class NativeInputWindowHandle : public gui::WindowInfoHandle {
-public:
- NativeInputWindowHandle(jweak objWeak);
- virtual ~NativeInputWindowHandle();
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env,
+ jobject inputWindowHandleObj);
- jobject getInputWindowHandleObjLocalRef(JNIEnv* env);
-
- virtual bool updateInfo();
-
-private:
- jweak mObjWeak;
-};
-
-extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
- JNIEnv* env, jobject inputWindowHandleObj);
-
-extern jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
- gui::WindowInfo windowInfo);
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+ const gui::WindowInfo& windowInfo);
} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 56292c3d0fb2..d3bf36e60345 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -979,14 +979,16 @@ static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj,
static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject inputWindow) {
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ if (!inputWindow) {
+ jniThrowNullPointerException(env, "InputWindowHandle is null");
+ return;
+ }
- sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
- env, inputWindow);
- handle->updateInfo();
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ sp<gui::WindowInfoHandle> info = android_view_InputWindowHandle_getHandle(env, inputWindow);
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- transaction->setInputWindowInfo(ctrl, *handle->getInfo());
+ transaction->setInputWindowInfo(ctrl, std::move(info));
}
static void nativeAddWindowInfosReportedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 606e829c41fa..6af742fb23f4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -104,6 +104,7 @@ message SecureSettingsProto {
optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_gesture_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 66c2e12f7cdf..0e4e22b09e24 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -158,6 +158,7 @@ android_app {
flags_packages: [
"android.app.appfunctions.flags-aconfig",
"android.app.contextualsearch.flags-aconfig",
+ "android.app.flags-aconfig",
"android.appwidget.flags-aconfig",
"android.content.pm.flags-aconfig",
"android.provider.flags-aconfig",
@@ -171,7 +172,9 @@ android_app {
"android.security.flags-aconfig",
"com.android.hardware.input.input-aconfig",
"aconfig_trade_in_mode_flags",
+ "art-aconfig-flags",
"ranging_aconfig_flags",
+ "aconfig_settingslib_flags",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5913992004b8..d09802a91edc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2632,13 +2632,22 @@
<!-- @SystemApi Allows access to perform vendor effects in the vibrator.
<p>Protection level: signature
- @FlaggedApi("android.os.vibrator.vendor_vibration_effects")
+ @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@hide
-->
<permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
android:protectionLevel="signature|privileged"
android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
+ <!-- @SystemApi Allows access to start a vendor vibration session.
+ <p>Protection level: signature
+ @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @hide
+ -->
+ <permission android:name="android.permission.START_VIBRATION_SESSIONS"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
+
<!-- @SystemApi Allows access to the vibrator state.
<p>Protection level: signature
@hide
@@ -4454,6 +4463,18 @@
android:description="@string/permdesc_hideOverlayWindows"
android:protectionLevel="normal" />
+ <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting
+ it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well
+ as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause}
+ to enter PiP when the user leaves the app.
+ This permission should only be used for certain PiP
+ <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>.
+ @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION)
+ -->
+ <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP"
+ android:protectionLevel="normal"
+ android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" />
+
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
<!-- ================================== -->
@@ -4960,6 +4981,27 @@
<permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
android:protectionLevel="signature|privileged|role" />
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+ Allows an application to access the Settings Preference services to read settings exposed
+ by the system Settings app and system apps that contribute settings surfaced by the
+ Settings app.
+ <p>This allows the calling application to read settings values through the host
+ application, agnostic of underlying storage. -->
+ <permission android:name="android.permission.READ_SYSTEM_PREFERENCES"
+ android:protectionLevel="signature|privileged|role"
+ android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+ Allows an application to access the Settings Preference services to write settings
+ values exposed by the system Settings app and system apps that contribute settings surfaced
+ in the Settings app.
+ <p>This allows the calling application to write settings values
+ through the host application, agnostic of underlying storage.
+ <p>Protection Level: signature|privileged|appop - appop to be added in followup -->
+ <permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -8482,6 +8524,16 @@
android:protectionLevel="signature|privileged"
android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
+ <!-- @SystemApi
+ @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS)
+ Ability to read program metadata and attach dynamic instrumentation.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
+
<!--
@TestApi
Signature permission reserved for testing. This should never be used to
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4d73f228ad0c..41dec3776b5c 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2273,6 +2273,22 @@
<attr name="enableOnBackInvokedCallback" format="boolean"/>
<attr name="intentMatchingFlags"/>
+
+ <!-- Specifies the set of drawable resources that can be used in place
+ of an existing declared icon or banner for activities that appear
+ in the app launcher. The resource referenced must be an array of
+ drawable resources and can contain at most 500 items.
+ {@link android.content.pm.PackageManager#changeLauncherIconConfig}
+ @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+ <attr name="alternateLauncherIcons" format="reference" />
+
+ <!-- Specifies the set of string resources that can be used in place
+ of an existing declared label for activities that appear
+ in the app launcher. The resource referenced must be an array of
+ string resources and can contain at most 500 items.
+ {@link android.content.pm.PackageManager#changeLauncherIconConfig}
+ @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+ <attr name="alternateLauncherLabels" format="reference" />
</declare-styleable>
<!-- An attribution is a logical part of an app and is identified by a tag.
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 70cc5f14391d..b6436d0b30a5 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -127,6 +127,10 @@
<public name="intentMatchingFlags"/>
<!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
<public name="layoutLabel"/>
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+ <public name="alternateLauncherIcons"/>
+ <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+ <public name="alternateLauncherLabels"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index da1fffa0fac4..a2598f69e031 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
@@ -30,8 +31,9 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.app.PropertyInvalidatedCache.Args;
import android.annotation.SuppressLint;
+import android.app.PropertyInvalidatedCache.Args;
+import android.os.Binder;
import com.android.internal.os.ApplicationSharedMemory;
import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -58,6 +60,7 @@ import org.junit.Test;
*/
@SmallTest
public class PropertyInvalidatedCacheTests {
+ @Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -455,8 +458,9 @@ public class PropertyInvalidatedCacheTests {
// Test the Args-style constructor.
@Test
public void testArgsConstructor() {
- // Create a cache with a maximum of four entries.
- TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+ // Create a cache with a maximum of four entries and non-isolated UIDs.
+ TestCache cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(false).api("init1"),
new TestQuery());
cache.invalidateCache();
@@ -570,4 +574,73 @@ public class PropertyInvalidatedCacheTests {
// Expected exception.
}
}
+
+ // Verify that a cache created with isolatedUids(true) separates out the results.
+ @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID)
+ @Test
+ public void testIsolatedUids() {
+ TestCache cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true),
+ new TestQuery());
+ cache.invalidateCache();
+ final int uid1 = 1;
+ final int uid2 = 2;
+
+ long token = Binder.setCallingWorkSourceUid(uid1);
+ try {
+ // Populate the cache for user 1
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo6", cache.query(6));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Populate the cache for user 2. User 1 values are not reused.
+ Binder.setCallingWorkSourceUid(uid2);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+
+ // Verify that the cache for user 1 is still populated.
+ Binder.setCallingWorkSourceUid(uid1);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+
+ } finally {
+ Binder.restoreCallingWorkSource(token);
+ }
+
+ // Repeat the test with a non-isolated cache.
+ cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true),
+ new TestQuery());
+ cache.invalidateCache();
+ token = Binder.setCallingWorkSourceUid(uid1);
+ try {
+ // Populate the cache for user 1
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo6", cache.query(6));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Populate the cache for user 2. User 1 values are reused.
+ Binder.setCallingWorkSourceUid(uid2);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Verify that the cache for user 1 is still populated.
+ Binder.setCallingWorkSourceUid(uid1);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+
+ } finally {
+ Binder.restoreCallingWorkSource(token);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
new file mode 100644
index 000000000000..a6de611cc077
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -0,0 +1,472 @@
+/*
+ * 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.hardware.display
+
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
+import android.view.Display
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class DisplayTopologyTest {
+ private var topology = DisplayTopology()
+
+ @Test
+ fun addOneDisplay() {
+ val displayId = 1
+ val width = 800f
+ val height = 600f
+
+ topology.addDisplay(displayId, width, height)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+
+ val display = topology.root!!
+ assertThat(display.displayId).isEqualTo(displayId)
+ assertThat(display.width).isEqualTo(width)
+ assertThat(display.height).isEqualTo(height)
+ assertThat(display.children).isEmpty()
+ }
+
+ @Test
+ fun addTwoDisplays() {
+ val displayId1 = 1
+ val width1 = 800f
+ val height1 = 600f
+
+ val displayId2 = 2
+ val width2 = 1000f
+ val height2 = 1500f
+
+ topology.addDisplay(displayId1, width1, height1)
+ topology.addDisplay(displayId2, width2, height2)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+ val display1 = topology.root!!
+ assertThat(display1.displayId).isEqualTo(displayId1)
+ assertThat(display1.width).isEqualTo(width1)
+ assertThat(display1.height).isEqualTo(height1)
+ assertThat(display1.children).hasSize(1)
+
+ val display2 = display1.children[0]
+ assertThat(display2.displayId).isEqualTo(displayId2)
+ assertThat(display2.width).isEqualTo(width2)
+ assertThat(display2.height).isEqualTo(height2)
+ assertThat(display2.children).isEmpty()
+ assertThat(display2.position).isEqualTo(POSITION_TOP)
+ assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+ }
+
+ @Test
+ fun addManyDisplays() {
+ val displayId1 = 1
+ val width1 = 800f
+ val height1 = 600f
+
+ val displayId2 = 2
+ val width2 = 1000f
+ val height2 = 1500f
+
+ topology.addDisplay(displayId1, width1, height1)
+ topology.addDisplay(displayId2, width2, height2)
+
+ val noOfDisplays = 30
+ for (i in 3..noOfDisplays) {
+ topology.addDisplay(/* displayId= */ i, width1, height1)
+ }
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+ val display1 = topology.root!!
+ assertThat(display1.displayId).isEqualTo(displayId1)
+ assertThat(display1.width).isEqualTo(width1)
+ assertThat(display1.height).isEqualTo(height1)
+ assertThat(display1.children).hasSize(1)
+
+ val display2 = display1.children[0]
+ assertThat(display2.displayId).isEqualTo(displayId2)
+ assertThat(display2.width).isEqualTo(width2)
+ assertThat(display2.height).isEqualTo(height2)
+ assertThat(display2.children).hasSize(1)
+ assertThat(display2.position).isEqualTo(POSITION_TOP)
+ assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+ var display = display2
+ for (i in 3..noOfDisplays) {
+ display = display.children[0]
+ assertThat(display.displayId).isEqualTo(i)
+ assertThat(display.width).isEqualTo(width1)
+ assertThat(display.height).isEqualTo(height1)
+ // The last display should have no children
+ assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+ assertThat(display.position).isEqualTo(POSITION_RIGHT)
+ assertThat(display.offset).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun removeDisplays() {
+ val displayId1 = 1
+ val width1 = 800f
+ val height1 = 600f
+
+ val displayId2 = 2
+ val width2 = 1000f
+ val height2 = 1500f
+
+ topology.addDisplay(displayId1, width1, height1)
+ topology.addDisplay(displayId2, width2, height2)
+
+ val noOfDisplays = 30
+ for (i in 3..noOfDisplays) {
+ topology.addDisplay(/* displayId= */ i, width1, height1)
+ }
+
+ var removedDisplays = arrayOf(20)
+ topology.removeDisplay(20)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+ var display1 = topology.root!!
+ assertThat(display1.displayId).isEqualTo(displayId1)
+ assertThat(display1.width).isEqualTo(width1)
+ assertThat(display1.height).isEqualTo(height1)
+ assertThat(display1.children).hasSize(1)
+
+ var display2 = display1.children[0]
+ assertThat(display2.displayId).isEqualTo(displayId2)
+ assertThat(display2.width).isEqualTo(width2)
+ assertThat(display2.height).isEqualTo(height2)
+ assertThat(display2.children).hasSize(1)
+ assertThat(display2.position).isEqualTo(POSITION_TOP)
+ assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+ var display = display2
+ for (i in 3..noOfDisplays) {
+ if (i in removedDisplays) {
+ continue
+ }
+ display = display.children[0]
+ assertThat(display.displayId).isEqualTo(i)
+ assertThat(display.width).isEqualTo(width1)
+ assertThat(display.height).isEqualTo(height1)
+ // The last display should have no children
+ assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+ assertThat(display.position).isEqualTo(POSITION_RIGHT)
+ assertThat(display.offset).isEqualTo(0)
+ }
+
+ topology.removeDisplay(22)
+ removedDisplays += 22
+ topology.removeDisplay(23)
+ removedDisplays += 23
+ topology.removeDisplay(25)
+ removedDisplays += 25
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+ display1 = topology.root!!
+ assertThat(display1.displayId).isEqualTo(displayId1)
+ assertThat(display1.width).isEqualTo(width1)
+ assertThat(display1.height).isEqualTo(height1)
+ assertThat(display1.children).hasSize(1)
+
+ display2 = display1.children[0]
+ assertThat(display2.displayId).isEqualTo(displayId2)
+ assertThat(display2.width).isEqualTo(width2)
+ assertThat(display2.height).isEqualTo(height2)
+ assertThat(display2.children).hasSize(1)
+ assertThat(display2.position).isEqualTo(POSITION_TOP)
+ assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+ display = display2
+ for (i in 3..noOfDisplays) {
+ if (i in removedDisplays) {
+ continue
+ }
+ display = display.children[0]
+ assertThat(display.displayId).isEqualTo(i)
+ assertThat(display.width).isEqualTo(width1)
+ assertThat(display.height).isEqualTo(height1)
+ // The last display should have no children
+ assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+ assertThat(display.position).isEqualTo(POSITION_RIGHT)
+ assertThat(display.offset).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun removeAllDisplays() {
+ val displayId = 1
+ val width = 800f
+ val height = 600f
+
+ topology.addDisplay(displayId, width, height)
+ topology.removeDisplay(displayId)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
+ assertThat(topology.root).isNull()
+ }
+
+ @Test
+ fun removeDisplayThatDoesNotExist() {
+ val displayId = 1
+ val width = 800f
+ val height = 600f
+
+ topology.addDisplay(displayId, width, height)
+ topology.removeDisplay(3)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+
+ val display = topology.root!!
+ assertThat(display.displayId).isEqualTo(displayId)
+ assertThat(display.width).isEqualTo(width)
+ assertThat(display.height).isEqualTo(height)
+ assertThat(display.children).isEmpty()
+ }
+
+ @Test
+ fun removePrimaryDisplay() {
+ val displayId1 = 1
+ val displayId2 = 2
+ val width = 800f
+ val height = 600f
+
+ topology = DisplayTopology(/* root= */ null, displayId2)
+ topology.addDisplay(displayId1, width, height)
+ topology.addDisplay(displayId2, width, height)
+ topology.removeDisplay(displayId2)
+
+ assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+ val display = topology.root!!
+ assertThat(display.displayId).isEqualTo(displayId1)
+ assertThat(display.width).isEqualTo(width)
+ assertThat(display.height).isEqualTo(height)
+ assertThat(display.children).isEmpty()
+ }
+
+ @Test
+ fun normalization_noOverlaps_leavesTopologyUnchanged() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ topology.normalize()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.root!!
+ assertThat(actualDisplay1.displayId).isEqualTo(1)
+ assertThat(actualDisplay1.width).isEqualTo(200f)
+ assertThat(actualDisplay1.height).isEqualTo(600f)
+ assertThat(actualDisplay1.children).hasSize(2)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ assertThat(actualDisplay2.displayId).isEqualTo(2)
+ assertThat(actualDisplay2.width).isEqualTo(600f)
+ assertThat(actualDisplay2.height).isEqualTo(200f)
+ assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.offset).isEqualTo(0f)
+ assertThat(actualDisplay2.children).hasSize(1)
+
+ val actualDisplay3 = actualDisplay1.children[1]
+ assertThat(actualDisplay3.displayId).isEqualTo(3)
+ assertThat(actualDisplay3.width).isEqualTo(600f)
+ assertThat(actualDisplay3.height).isEqualTo(200f)
+ assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.offset).isEqualTo(400f)
+ assertThat(actualDisplay3.children).isEmpty()
+
+ val actualDisplay4 = actualDisplay2.children[0]
+ assertThat(actualDisplay4.displayId).isEqualTo(4)
+ assertThat(actualDisplay4.width).isEqualTo(200f)
+ assertThat(actualDisplay4.height).isEqualTo(600f)
+ assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.offset).isEqualTo(0f)
+ assertThat(actualDisplay4.children).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveDisplayWithoutReparenting() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
+ topology.normalize()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.root!!
+ assertThat(actualDisplay1.displayId).isEqualTo(1)
+ assertThat(actualDisplay1.width).isEqualTo(200f)
+ assertThat(actualDisplay1.height).isEqualTo(600f)
+ assertThat(actualDisplay1.children).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ assertThat(actualDisplay2.displayId).isEqualTo(2)
+ assertThat(actualDisplay2.width).isEqualTo(200f)
+ assertThat(actualDisplay2.height).isEqualTo(600f)
+ assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.offset).isEqualTo(0f)
+ assertThat(actualDisplay2.children).hasSize(2)
+
+ val actualDisplay3 = actualDisplay2.children[1]
+ assertThat(actualDisplay3.displayId).isEqualTo(3)
+ assertThat(actualDisplay3.width).isEqualTo(600f)
+ assertThat(actualDisplay3.height).isEqualTo(200f)
+ assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.offset).isEqualTo(10f)
+ assertThat(actualDisplay3.children).isEmpty()
+
+ val actualDisplay4 = actualDisplay2.children[0]
+ assertThat(actualDisplay4.displayId).isEqualTo(4)
+ assertThat(actualDisplay4.width).isEqualTo(200f)
+ assertThat(actualDisplay4.height).isEqualTo(600f)
+ assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.offset).isEqualTo(210f)
+ assertThat(actualDisplay4.children).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 50f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+ display1.addChild(display3)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ // Display 3 gets moved and its left side is still on the same line as the right side
+ // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
+ // becomes its new parent.
+ topology.normalize()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.root!!
+ assertThat(actualDisplay1.displayId).isEqualTo(1)
+ assertThat(actualDisplay1.width).isEqualTo(200f)
+ assertThat(actualDisplay1.height).isEqualTo(50f)
+ assertThat(actualDisplay1.children).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ assertThat(actualDisplay2.displayId).isEqualTo(2)
+ assertThat(actualDisplay2.width).isEqualTo(600f)
+ assertThat(actualDisplay2.height).isEqualTo(200f)
+ assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.offset).isEqualTo(0f)
+ assertThat(actualDisplay2.children).hasSize(1)
+
+ val actualDisplay3 = actualDisplay2.children[0]
+ assertThat(actualDisplay3.displayId).isEqualTo(3)
+ assertThat(actualDisplay3.width).isEqualTo(600f)
+ assertThat(actualDisplay3.height).isEqualTo(200f)
+ assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM)
+ assertThat(actualDisplay3.offset).isEqualTo(0f)
+ assertThat(actualDisplay3.children).isEmpty()
+ }
+
+ @Test
+ fun normalization_moveAndReparentDisplay() {
+ val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+ /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+ val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display1.addChild(display2)
+
+ val primaryDisplayId = 3
+ val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+ /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+ display1.addChild(display3)
+
+ val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+ /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+ display2.addChild(display4)
+
+ topology = DisplayTopology(display1, primaryDisplayId)
+ topology.normalize()
+
+ assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+ val actualDisplay1 = topology.root!!
+ assertThat(actualDisplay1.displayId).isEqualTo(1)
+ assertThat(actualDisplay1.width).isEqualTo(200f)
+ assertThat(actualDisplay1.height).isEqualTo(600f)
+ assertThat(actualDisplay1.children).hasSize(1)
+
+ val actualDisplay2 = actualDisplay1.children[0]
+ assertThat(actualDisplay2.displayId).isEqualTo(2)
+ assertThat(actualDisplay2.width).isEqualTo(200f)
+ assertThat(actualDisplay2.height).isEqualTo(600f)
+ assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay2.offset).isEqualTo(0f)
+ assertThat(actualDisplay2.children).hasSize(1)
+
+ val actualDisplay3 = actualDisplay2.children[0]
+ assertThat(actualDisplay3.displayId).isEqualTo(3)
+ assertThat(actualDisplay3.width).isEqualTo(600f)
+ assertThat(actualDisplay3.height).isEqualTo(200f)
+ assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay3.offset).isEqualTo(400f)
+ assertThat(actualDisplay3.children).hasSize(1)
+
+ val actualDisplay4 = actualDisplay3.children[0]
+ assertThat(actualDisplay4.displayId).isEqualTo(4)
+ assertThat(actualDisplay4.width).isEqualTo(200f)
+ assertThat(actualDisplay4.height).isEqualTo(600f)
+ assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+ assertThat(actualDisplay4.offset).isEqualTo(-400f)
+ assertThat(actualDisplay4.children).isEmpty()
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 4c5b7e508e34..8932cf1ba552 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -78,6 +78,23 @@ public class DisplayInfoTest {
}
@Test
+ public void testRefreshRateOverride_keepsDisplyInfosEqualWhenOverrideIsSame() {
+ Display.Mode mode = new Display.Mode(
+ /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120);
+ DisplayInfo displayInfo1 = new DisplayInfo();
+ setSupportedMode(displayInfo1, mode);
+ displayInfo1.renderFrameRate = 60;
+ displayInfo1.refreshRateOverride = 30;
+
+ DisplayInfo displayInfo2 = new DisplayInfo();
+ setSupportedMode(displayInfo2, mode);
+ displayInfo2.renderFrameRate = 30;
+ displayInfo2.refreshRateOverride = 30;
+
+ assertTrue(displayInfo1.equals(displayInfo2));
+ }
+
+ @Test
public void testRefreshRateOverride_makeDisplayInfosDifferent() {
Display.Mode mode = new Display.Mode(
/*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120);
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index 6210a00a5940..09bfadbf56a4 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -110,8 +110,9 @@ public class VibratorTest {
@Test
public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
+ int[] vibratorIds = new int[0];
VibratorManager mockVibratorManager = mock(VibratorManager.class);
- when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+ when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
Vibrator.OnVibratorStateChangedListener mockListener =
mock(Vibrator.OnVibratorStateChangedListener.class);
@@ -119,7 +120,7 @@ public class VibratorTest {
new SystemVibrator.MultiVibratorStateListener(
mTestLooper.getNewExecutor(), mockListener);
- multiVibratorListener.register(mockVibratorManager);
+ multiVibratorListener.register(mockVibratorManager, vibratorIds);
// Never tries to register a listener to an individual vibrator.
assertFalse(multiVibratorListener.hasRegisteredListeners());
@@ -128,8 +129,9 @@ public class VibratorTest {
@Test
public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() {
+ int[] vibratorIds = new int[] { 1 };
VibratorManager mockVibratorManager = mock(VibratorManager.class);
- when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 });
+ when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
Vibrator.OnVibratorStateChangedListener mockListener =
@@ -138,7 +140,7 @@ public class VibratorTest {
new SystemVibrator.MultiVibratorStateListener(
mTestLooper.getNewExecutor(), mockListener);
- multiVibratorListener.register(mockVibratorManager);
+ multiVibratorListener.register(mockVibratorManager, vibratorIds);
assertTrue(multiVibratorListener.hasRegisteredListeners());
multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
@@ -156,8 +158,9 @@ public class VibratorTest {
@Test
public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() {
+ int[] vibratorIds = new int[] { 1, 2 };
VibratorManager mockVibratorManager = mock(VibratorManager.class);
- when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
Vibrator.OnVibratorStateChangedListener mockListener =
@@ -166,7 +169,7 @@ public class VibratorTest {
new SystemVibrator.MultiVibratorStateListener(
mTestLooper.getNewExecutor(), mockListener);
- multiVibratorListener.register(mockVibratorManager);
+ multiVibratorListener.register(mockVibratorManager, vibratorIds);
assertTrue(multiVibratorListener.hasRegisteredListeners());
multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
@@ -181,8 +184,9 @@ public class VibratorTest {
@Test
public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() {
+ int[] vibratorIds = new int[] { 1, 2 };
VibratorManager mockVibratorManager = mock(VibratorManager.class);
- when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+ when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
Vibrator.OnVibratorStateChangedListener mockListener =
@@ -191,7 +195,7 @@ public class VibratorTest {
new SystemVibrator.MultiVibratorStateListener(
mTestLooper.getNewExecutor(), mockListener);
- multiVibratorListener.register(mockVibratorManager);
+ multiVibratorListener.register(mockVibratorManager, vibratorIds);
assertTrue(multiVibratorListener.hasRegisteredListeners());
multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7b96699f7f71..857df1038df1 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -213,6 +213,8 @@
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" />
+ <assign-permission name="android.permission.DYNAMIC_INSTRUMENTATION" uid="uprobestats" />
+
<split-permission name="android.permission.ACCESS_FINE_LOCATION">
<new-permission name="android.permission.ACCESS_COARSE_LOCATION" />
</split-permission>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 56e55df3f27c..541ca602a386 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -402,6 +402,9 @@ applications that come with the platform
<permission name="android.permission.SHOW_CUSTOMIZED_RESOLVER"/>
<!-- Permission required for access VIBRATOR_STATE. -->
<permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
+ <!-- Permission required for vendor vibration effects and sessions. -->
+ <permission name="android.permission.VIBRATE_VENDOR_EFFECTS"/>
+ <permission name="android.permission.START_VIBRATION_SESSIONS"/>
<!-- Permission required for UsageStatsTest CTS test. -->
<permission name="android.permission.MANAGE_NOTIFICATIONS"/>
<!-- Permission required for CompanionDeviceManager CTS test. -->
@@ -591,6 +594,9 @@ applications that come with the platform
<!-- Permission required for CTS test - AdvancedProtectionManagerTest -->
<permission name="android.permission.SET_ADVANCED_PROTECTION_MODE" />
<permission name="android.permission.QUERY_ADVANCED_PROTECTION_MODE" />
+ <!-- Permissions required for CTS test - SettingsPreferenceServiceClientTest -->
+ <permission name="android.permission.READ_SYSTEM_PREFERENCES" />
+ <permission name="android.permission.WRITE_SYSTEM_PREFERENCES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f8d3bffbe00b..2b0802b54c14 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -171,7 +171,7 @@ key 143 WAKEUP
# key 149 "KEY_PROG2"
key 150 EXPLORER
# key 151 "KEY_MSDOS"
-key 152 POWER
+key 152 LOCK
# key 153 "KEY_DIRECTION"
# key 154 "KEY_CYCLEWINDOWS"
key 155 ENVELOPE
@@ -200,20 +200,20 @@ key 177 PAGE_UP
key 178 PAGE_DOWN
key 179 NUMPAD_LEFT_PAREN
key 180 NUMPAD_RIGHT_PAREN
-# key 181 "KEY_NEW"
+key 181 NEW
# key 182 "KEY_REDO"
-# key 183 F13
-# key 184 F14
-# key 185 F15
-# key 186 F16
-# key 187 F17
-# key 188 F18
-# key 189 F19
-# key 190 F20
-# key 191 F21
-# key 192 F22
-# key 193 F23
-# key 194 F24
+key 183 F13
+key 184 F14
+key 185 F15
+key 186 F16
+key 187 F17
+key 188 F18
+key 189 F19
+key 190 F20
+key 191 F21
+key 192 F22
+key 193 F23
+key 194 F24
# key 195 (undefined)
# key 196 (undefined)
# key 197 (undefined)
@@ -225,11 +225,11 @@ key 201 MEDIA_PAUSE
# key 203 "KEY_PROG4"
key 204 NOTIFICATION
# key 205 "KEY_SUSPEND"
-# key 206 "KEY_CLOSE"
+key 206 CLOSE
key 207 MEDIA_PLAY
key 208 MEDIA_FAST_FORWARD
# key 209 "KEY_BASSBOOST"
-# key 210 "KEY_PRINT"
+key 210 PRINT
# key 211 "KEY_HP"
key 212 CAMERA
key 213 MUSIC
@@ -328,7 +328,7 @@ key 368 LANGUAGE_SWITCH
# key 369 "KEY_TITLE"
key 370 CAPTIONS
# key 371 "KEY_ANGLE"
-# key 372 "KEY_ZOOM"
+key 372 FULLSCREEN
# key 373 "KEY_MODE"
# key 374 "KEY_KEYBOARD"
# key 375 "KEY_SCREEN"
@@ -425,12 +425,15 @@ key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
key 585 EMOJI_PICKER
+key 586 DICTATE
key 656 MACRO_1
key 657 MACRO_2
key 658 MACRO_3
key 659 MACRO_4
# Keys defined by HID usages
+key usage 0x010082 LOCK FALLBACK_USAGE_MAPPING
+key usage 0x01009B DO_NOT_DISTURB FALLBACK_USAGE_MAPPING
key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
@@ -438,12 +441,17 @@ key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING
key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING
key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING
key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c00D8 DICTATE FALLBACK_USAGE_MAPPING
key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING
key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING
key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING
key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING
key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING
+key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING
+key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING
+key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING
key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING
+key usage 0x0c0232 FULLSCREEN FALLBACK_USAGE_MAPPING
key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING
key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING
key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4c47de0ca754..d55a71e21931 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1761,7 +1761,7 @@ public abstract class ColorSpace {
if (Flags.displayBt2020Colorspace()) {
sNamedColorSpaceMap.put(Named.DISPLAY_BT2020.ordinal(), new ColorSpace.Rgb(
- "BT 2020",
+ "Display BT. 2020",
BT2020_PRIMARIES,
ILLUMINANT_D65,
null,
diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java
index 197aaba4bcb5..e6091c1da8a5 100644
--- a/keystore/java/android/security/keystore/KeyStoreManager.java
+++ b/keystore/java/android/security/keystore/KeyStoreManager.java
@@ -49,7 +49,7 @@ import java.util.List;
*/
@FlaggedApi(android.security.Flags.FLAG_KEYSTORE_GRANT_API)
@SystemService(Context.KEYSTORE_SERVICE)
-public class KeyStoreManager {
+public final class KeyStoreManager {
private static final String TAG = "KeyStoreManager";
private static final Object sInstanceLock = new Object();
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 501bedd50f55..c2755ef6ccb6 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -19,6 +19,7 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical"
+ android:clipChildren="false"
android:id="@+id/bubble_expanded_view">
<com.android.wm.shell.bubbles.bar.BubbleBarHandleView
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index f1ecde49ce78..7aca921dccc7 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -14,20 +14,18 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
+ android:clipToPadding="false"
android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
android:orientation="vertical"
- android:elevation="@dimen/bubble_manage_menu_elevation"
- android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
- android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
- android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
- android:clipToPadding="false">
+ android:visibility="invisible"
+ tools:visibility="visible">
<LinearLayout
android:id="@+id/bubble_bar_manage_menu_bubble_section"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 2a50e4d0d74b..272dfecb0bf9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -222,7 +222,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate());
}
- mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+ mMenuViewController = new BubbleBarMenuViewController(mContext, mHandleView, this);
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
@Override
public void onMenuVisibilityChanged(boolean visible) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index e781c07f01a7..712e41b0b3c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -17,17 +17,18 @@ package com.android.wm.shell.bubbles.bar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Path;
-import android.graphics.RectF;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewOutlineProvider;
import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.IntProperty;
import androidx.core.content.ContextCompat;
import com.android.wm.shell.R;
@@ -37,14 +38,33 @@ import com.android.wm.shell.R;
*/
public class BubbleBarHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
- // Path used to draw the dots
- private final Path mPath = new Path();
+ /** Custom property to set handle color. */
+ private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>(
+ "handleColor") {
+ @Override
+ public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) {
+ bubbleBarHandleView.setHandleColor(color);
+ }
+
+ @Override
+ public Integer get(BubbleBarHandleView bubbleBarHandleView) {
+ return bubbleBarHandleView.getHandleColor();
+ }
+ };
+
+ @VisibleForTesting
+ final Paint mHandlePaint = new Paint();
private final @ColorInt int mHandleLightColor;
private final @ColorInt int mHandleDarkColor;
- private @ColorInt int mCurrentColor;
+ private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance();
+ private final float mHandleHeight;
+ private final float mHandleWidth;
+ private float mCurrentHandleHeight;
+ private float mCurrentHandleWidth;
@Nullable
private ObjectAnimator mColorChangeAnim;
+ private @ColorInt int mRegionSamplerColor;
public BubbleBarHandleView(Context context) {
this(context, null /* attrs */);
@@ -61,30 +81,52 @@ public class BubbleBarHandleView extends View {
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- final int handleHeight = getResources().getDimensionPixelSize(
+ mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mHandlePaint.setStyle(Paint.Style.FILL);
+ mHandlePaint.setColor(0);
+ mHandleHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_bar_expanded_view_handle_height);
+ mHandleWidth = getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_caption_width);
mHandleLightColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_light);
mHandleDarkColor = ContextCompat.getColor(getContext(),
R.color.bubble_bar_expanded_view_handle_dark);
-
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- final int handleCenterY = view.getHeight() / 2;
- final int handleTop = handleCenterY - handleHeight / 2;
- final int handleBottom = handleTop + handleHeight;
- final int radius = handleHeight / 2;
- RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom);
- mPath.reset();
- mPath.addRoundRect(handle, radius, radius, Path.Direction.CW);
- outline.setPath(mPath);
- }
- });
+ mCurrentHandleHeight = mHandleHeight;
+ mCurrentHandleWidth = mHandleWidth;
setContentDescription(getResources().getString(R.string.handle_text));
}
+ private void setHandleColor(int color) {
+ mHandlePaint.setColor(color);
+ invalidate();
+ }
+
+ private int getHandleColor() {
+ return mHandlePaint.getColor();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ float handleLeft = (getWidth() - mCurrentHandleWidth) / 2;
+ float handleRight = handleLeft + mCurrentHandleWidth;
+ float handleCenterY = (float) getHeight() / 2;
+ float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2);
+ float handleBottom = handleTop + mCurrentHandleHeight;
+ float cornerRadius = mCurrentHandleHeight / 2;
+ canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius,
+ cornerRadius, mHandlePaint);
+ }
+
+ /** Sets handle width, height and color. Does not change the layout properties */
+ private void setHandleProperties(float width, float height, int color) {
+ mCurrentHandleHeight = height;
+ mCurrentHandleWidth = width;
+ mHandlePaint.setColor(color);
+ invalidate();
+ }
+
/**
* Updates the handle color.
*
@@ -94,15 +136,15 @@ public class BubbleBarHandleView extends View {
*/
public void updateHandleColor(boolean isRegionDark, boolean animated) {
int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
- if (newColor == mCurrentColor) {
+ if (newColor == mRegionSamplerColor) {
return;
}
+ mRegionSamplerColor = newColor;
if (mColorChangeAnim != null) {
mColorChangeAnim.cancel();
}
- mCurrentColor = newColor;
if (animated) {
- mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+ mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor);
mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -112,7 +154,39 @@ public class BubbleBarHandleView extends View {
mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
mColorChangeAnim.start();
} else {
- setBackgroundColor(newColor);
+ setHandleColor(newColor);
}
}
+
+ /** Returns handle padding top. */
+ public int getHandlePaddingTop() {
+ return (getHeight() - getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_handle_height)) / 2;
+ }
+
+ /** Animates handle for the bubble menu. */
+ public void animateHandleForMenu(float progress, float widthDelta, float heightDelta,
+ int menuColor) {
+ float currentWidth = mHandleWidth + widthDelta * progress;
+ float currentHeight = mHandleHeight + heightDelta * progress;
+ int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor);
+ setHandleProperties(currentWidth, currentHeight, color);
+ setTranslationY(heightDelta * progress / 2);
+ }
+
+ /** Restores all the properties that were animated to the default values. */
+ public void restoreAnimationDefaults() {
+ setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor);
+ setTranslationY(0);
+ }
+
+ /** Returns the handle height. */
+ public int getHandleHeight() {
+ return (int) mHandleHeight;
+ }
+
+ /** Returns the handle width. */
+ public int getHandleWidth() {
+ return (int) mHandleWidth;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 999ce17905ef..1f77abe54c8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.bar;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
+import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import android.annotation.Nullable;
import android.content.Context;
@@ -66,8 +67,6 @@ public class BubbleBarLayerView extends FrameLayout
private static final String TAG = BubbleBarLayerView.class.getSimpleName();
- private static final float SCRIM_ALPHA = 0.2f;
-
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
@@ -386,7 +385,7 @@ public class BubbleBarLayerView extends FrameLayout
if (show) {
mScrimView.animate()
.setInterpolator(ALPHA_IN)
- .alpha(SCRIM_ALPHA)
+ .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
.start();
} else {
mScrimView.animate()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index 0ee20ef1731f..99e20097e61c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -47,6 +47,10 @@ public class BubbleBarMenuView extends LinearLayout {
private ImageView mBubbleIconView;
private ImageView mBubbleDismissIconView;
private TextView mBubbleTitleView;
+ // The animation has three stages. Each stage transition lasts until the animation ends. In
+ // stage 1, the title item content fades in. In stage 2, the background of the option items
+ // fades in. In stage 3, the option item content fades in.
+ private static final int SHOW_MENU_STAGES_COUNT = 3;
public BubbleBarMenuView(Context context) {
this(context, null /* attrs */);
@@ -97,6 +101,35 @@ public class BubbleBarMenuView extends LinearLayout {
}
}
+ /** Animates the menu from the specified start scale. */
+ public void animateFromStartScale(float currentScale, float progress) {
+ int menuItemElevation = getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_menu_elevation);
+ setScaleX(currentScale);
+ setScaleY(currentScale);
+ setAlphaForTitleViews(progress);
+ mBubbleSectionView.setElevation(menuItemElevation * progress);
+ float actionsBackgroundAlpha = Math.max(0,
+ (progress - (float) 1 / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1));
+ float actionItemsAlpha = Math.max(0,
+ (progress - (float) 2 / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT);
+ mActionsSectionView.setAlpha(actionsBackgroundAlpha);
+ mActionsSectionView.setElevation(menuItemElevation * actionsBackgroundAlpha);
+ setMenuItemViewsAlpha(actionItemsAlpha);
+ }
+
+ private void setAlphaForTitleViews(float alpha) {
+ mBubbleIconView.setAlpha(alpha);
+ mBubbleTitleView.setAlpha(alpha);
+ mBubbleDismissIconView.setAlpha(alpha);
+ }
+
+ private void setMenuItemViewsAlpha(float alpha) {
+ for (int i = mActionsSectionView.getChildCount() - 1; i >= 0; i--) {
+ mActionsSectionView.getChildAt(i).setAlpha(alpha);
+ }
+ }
+
/** Update menu details with bubble info */
void updateInfo(Bubble bubble) {
if (bubble.getIcon() != null) {
@@ -153,6 +186,11 @@ public class BubbleBarMenuView extends LinearLayout {
return mBubbleSectionView.getAlpha();
}
+ /** Return title menu item height. */
+ public float getTitleItemHeight() {
+ return mBubbleSectionView.getHeight();
+ }
+
/**
* Menu action details used to create menu items
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 514810745e10..9dd0cae20370 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -15,6 +15,9 @@
*/
package com.android.wm.shell.bubbles.bar;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -26,13 +29,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
+import com.android.app.animation.Interpolators;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.shared.animation.PhysicsAnimator;
import java.util.ArrayList;
@@ -40,22 +40,26 @@ import java.util.ArrayList;
* Manages bubble bar expanded view menu presentation and animations
*/
class BubbleBarMenuViewController {
- private static final float MENU_INITIAL_SCALE = 0.5f;
+
+ private static final float WIDTH_SWAP_FRACTION = 0.4F;
+ private static final long MENU_ANIMATION_DURATION = 600;
+
private final Context mContext;
private final ViewGroup mRootView;
+ private final BubbleBarHandleView mHandleView;
private @Nullable Listener mListener;
private @Nullable Bubble mBubble;
private @Nullable BubbleBarMenuView mMenuView;
/** A transparent view used to intercept touches to collapse menu when presented */
private @Nullable View mScrimView;
- private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
- private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+ private @Nullable ValueAnimator mMenuAnimator;
+
- BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+ BubbleBarMenuViewController(Context context, BubbleBarHandleView handleView,
+ ViewGroup rootView) {
mContext = context;
mRootView = rootView;
- mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+ mHandleView = handleView;
}
/** Tells if the menu is visible or being animated */
@@ -81,20 +85,21 @@ class BubbleBarMenuViewController {
if (mMenuView == null || mScrimView == null) {
setupMenu();
}
- cancelAnimations();
- mMenuView.setVisibility(View.VISIBLE);
- mScrimView.setVisibility(View.VISIBLE);
- Runnable endActions = () -> {
- mMenuView.getChildAt(0).requestAccessibilityFocus();
- if (mListener != null) {
- mListener.onMenuVisibilityChanged(true /* isShown */);
+ runOnMenuIsMeasured(() -> {
+ mMenuView.setVisibility(View.VISIBLE);
+ mScrimView.setVisibility(View.VISIBLE);
+ Runnable endActions = () -> {
+ mMenuView.getChildAt(0).requestAccessibilityFocus();
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(true /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(true /* show */, endActions);
+ } else {
+ endActions.run();
}
- };
- if (animated) {
- animateTransition(true /* show */, endActions);
- } else {
- endActions.run();
- }
+ });
}
/**
@@ -103,18 +108,30 @@ class BubbleBarMenuViewController {
*/
void hideMenu(boolean animated) {
if (mMenuView == null || mScrimView == null) return;
- cancelAnimations();
- Runnable endActions = () -> {
- mMenuView.setVisibility(View.GONE);
- mScrimView.setVisibility(View.GONE);
- if (mListener != null) {
- mListener.onMenuVisibilityChanged(false /* isShown */);
+ runOnMenuIsMeasured(() -> {
+ Runnable endActions = () -> {
+ mHandleView.restoreAnimationDefaults();
+ mMenuView.setVisibility(View.GONE);
+ mScrimView.setVisibility(View.GONE);
+ mHandleView.setVisibility(View.VISIBLE);
+ if (mListener != null) {
+ mListener.onMenuVisibilityChanged(false /* isShown */);
+ }
+ };
+ if (animated) {
+ animateTransition(false /* show */, endActions);
+ } else {
+ endActions.run();
}
- };
- if (animated) {
- animateTransition(false /* show */, endActions);
+ });
+ }
+
+ private void runOnMenuIsMeasured(Runnable action) {
+ if (mMenuView.getWidth() == 0 || mMenuView.getHeight() == 0) {
+ // the menu view is not yet measured, postpone showing the animation
+ mMenuView.post(() -> runOnMenuIsMeasured(action));
} else {
- endActions.run();
+ action.run();
}
}
@@ -125,24 +142,63 @@ class BubbleBarMenuViewController {
*/
private void animateTransition(boolean show, Runnable endActions) {
if (mMenuView == null) return;
- mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
- mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
- mMenuAnimator
- .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
- .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
- .withEndActions(() -> {
- mMenuAnimator = null;
- endActions.run();
- })
- .start();
+ float startValue = show ? 0 : 1;
+ if (mMenuAnimator != null && mMenuAnimator.isRunning()) {
+ startValue = (float) mMenuAnimator.getAnimatedValue();
+ mMenuAnimator.cancel();
+ }
+ ValueAnimator showMenuAnimation = ValueAnimator.ofFloat(startValue, show ? 1 : 0);
+ showMenuAnimation.setDuration(MENU_ANIMATION_DURATION);
+ showMenuAnimation.setInterpolator(Interpolators.EMPHASIZED);
+ showMenuAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mMenuAnimator = null;
+ endActions.run();
+ }
+ });
+ mMenuAnimator = showMenuAnimation;
+ setupAnimatorListener(showMenuAnimation);
+ showMenuAnimation.start();
}
- /** Cancel running animations */
- private void cancelAnimations() {
- if (mMenuAnimator != null) {
- mMenuAnimator.cancel();
- mMenuAnimator = null;
+ /** Setup listener that orchestrates the animation. */
+ private void setupAnimatorListener(ValueAnimator showMenuAnimation) {
+ // Getting views properties start values
+ int widthDiff = mMenuView.getWidth() - mHandleView.getHandleWidth();
+ int handleHeight = mHandleView.getHandleHeight();
+ float targetWidth = mHandleView.getHandleWidth() + widthDiff * WIDTH_SWAP_FRACTION;
+ float targetHeight = targetWidth * mMenuView.getTitleItemHeight() / mMenuView.getWidth();
+ int menuColor;
+ try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+ com.android.internal.R.attr.materialColorSurfaceBright,
+ })) {
+ menuColor = ta.getColor(0, Color.WHITE);
}
+ // Calculating deltas
+ float swapScale = targetWidth / mMenuView.getWidth();
+ float handleWidthDelta = targetWidth - mHandleView.getHandleWidth();
+ float handleHeightDelta = targetHeight - handleHeight;
+ // Setting update listener that will orchestrate the animation
+ showMenuAnimation.addUpdateListener(animator -> {
+ float animationProgress = (float) animator.getAnimatedValue();
+ boolean showHandle = animationProgress <= WIDTH_SWAP_FRACTION;
+ mHandleView.setVisibility(showHandle ? View.VISIBLE : View.GONE);
+ mMenuView.setVisibility(showHandle ? View.GONE : View.VISIBLE);
+ if (showHandle) {
+ float handleAnimationProgress = animationProgress / WIDTH_SWAP_FRACTION;
+ mHandleView.animateHandleForMenu(handleAnimationProgress, handleWidthDelta,
+ handleHeightDelta, menuColor);
+ } else {
+ mMenuView.setTranslationY(mHandleView.getHandlePaddingTop());
+ mMenuView.setPivotY(0);
+ mMenuView.setPivotX((float) mMenuView.getWidth() / 2);
+ float menuAnimationProgress =
+ (animationProgress - WIDTH_SWAP_FRACTION) / (1 - WIDTH_SWAP_FRACTION);
+ float currentMenuScale = swapScale + (1 - swapScale) * menuAnimationProgress;
+ mMenuView.animateFromStartScale(currentMenuScale, menuAnimationProgress);
+ }
+ });
}
/** Sets up and inflate menu views */
@@ -150,9 +206,6 @@ class BubbleBarMenuViewController {
// Menu view setup
mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
R.layout.bubble_bar_menu_view, mRootView, false);
- mMenuView.setAlpha(0f);
- mMenuView.setPivotY(0f);
- mMenuView.setScaleY(MENU_INITIAL_SCALE);
mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */));
if (mBubble != null) {
mMenuView.updateInfo(mBubble);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
index 4abb35c2a428..193c593e2ab2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -16,8 +16,11 @@
package com.android.wm.shell.common.pip
import android.app.AppOpsManager
+import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.util.Pair
+import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.common.ShellExecutor
class PipAppOpsListener(
@@ -27,10 +30,12 @@ class PipAppOpsListener(
) {
private val mAppOpsManager: AppOpsManager = checkNotNull(
mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+ private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> =
+ PipUtils::getTopPipActivity
private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
try {
// Dismiss the PiP once the user disables the app ops setting for that package
- val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+ val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext)
val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
val userId = topPipActivityInfo.second
val appInfo = mContext.packageManager
@@ -75,4 +80,9 @@ class PipAppOpsListener(
/** Dismisses the PIP window. */
fun dismissPip()
}
+
+ @VisibleForTesting
+ fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) {
+ mTopPipActivityInfoSupplier = supplier
+ }
} \ No newline at end of file
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 a472f79c98e6..44fce81fa059 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
@@ -836,14 +836,21 @@ public abstract class WMShellModule {
@Provides
static Optional<DesktopImmersiveController> provideDesktopImmersiveController(
Context context,
+ ShellInit shellInit,
Transitions transitions,
@DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
- ShellTaskOrganizer shellTaskOrganizer) {
+ ShellTaskOrganizer shellTaskOrganizer,
+ ShellCommandHandler shellCommandHandler) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopImmersiveController(
- transitions, desktopRepository, displayController, shellTaskOrganizer));
+ shellInit,
+ transitions,
+ desktopRepository,
+ displayController,
+ shellTaskOrganizer,
+ shellCommandHandler));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 3a4764d45f2c..3cd5df3121c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip;
import android.content.Context;
import android.os.Handler;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -41,6 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -169,6 +171,8 @@ public abstract class Pip1Module {
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -176,7 +180,8 @@ public abstract class Pip1Module {
syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 8d1b15c1e631..78e676f8cd45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -22,6 +22,7 @@ import android.os.SystemClock;
import androidx.annotation.NonNull;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -214,6 +215,7 @@ public abstract class TvPipModule {
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -221,8 +223,9 @@ public abstract class TvPipModule {
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index f69aa6df6a1d..1acde73e68dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -34,10 +34,13 @@ import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.transition.Transitions.TransitionObserver
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+import java.io.PrintWriter
/**
* A controller to move tasks in/out of desktop's full immersive state where the task
@@ -45,27 +48,34 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
* be transient below the status bar like in fullscreen immersive mode.
*/
class DesktopImmersiveController(
+ shellInit: ShellInit,
private val transitions: Transitions,
private val desktopRepository: DesktopRepository,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val shellCommandHandler: ShellCommandHandler,
private val transactionSupplier: () -> SurfaceControl.Transaction,
) : TransitionHandler, TransitionObserver {
constructor(
+ shellInit: ShellInit,
transitions: Transitions,
desktopRepository: DesktopRepository,
displayController: DisplayController,
shellTaskOrganizer: ShellTaskOrganizer,
+ shellCommandHandler: ShellCommandHandler,
) : this(
+ shellInit,
transitions,
desktopRepository,
displayController,
shellTaskOrganizer,
+ shellCommandHandler,
{ SurfaceControl.Transaction() }
)
- private var state: TransitionState? = null
+ @VisibleForTesting
+ var state: TransitionState? = null
@VisibleForTesting
val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
@@ -79,10 +89,21 @@ class DesktopImmersiveController(
/** A listener to invoke on animation changes during entry/exit. */
var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ fun onInit() {
+ shellCommandHandler.addDumpCallback(this::dump, this)
+ }
+
/** Starts a transition to enter full immersive state inside the desktop. */
fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start entry because transition already in progress.")
+ logV(
+ "Cannot start entry because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
val wct = WindowContainerTransaction().apply {
@@ -100,7 +121,10 @@ class DesktopImmersiveController(
fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start exit because transition already in progress.")
+ logV(
+ "Cannot start exit because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
@@ -225,14 +249,19 @@ class DesktopImmersiveController(
finishCallback: Transitions.TransitionFinishCallback
): Boolean {
val state = requireState()
- if (transition != state.transition) return false
+ check(state.transition == transition) {
+ "Transition $transition did not match expected state=$state"
+ }
logD("startAnimation transition=%s", transition)
animateResize(
targetTaskId = state.taskId,
info = info,
startTransaction = startTransaction,
finishTransaction = finishTransaction,
- finishCallback = finishCallback
+ finishCallback = {
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ clearState()
+ },
)
return true
}
@@ -242,12 +271,18 @@ class DesktopImmersiveController(
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
) {
logD("animateResize for task#%d", targetTaskId)
- val change = info.changes.first { c ->
+ val change = info.changes.firstOrNull { c ->
val taskInfo = c.taskInfo
- return@first taskInfo != null && taskInfo.taskId == targetTaskId
+ return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+ }
+ if (change == null) {
+ logD("Did not find change for task#%d to animate", targetTaskId)
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ return
}
animateResizeChange(change, startTransaction, finishTransaction, finishCallback)
}
@@ -288,7 +323,6 @@ class DesktopImmersiveController(
.apply()
onTaskResizeAnimationListener?.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null /* wct */)
- clearState()
}
)
addUpdateListener { animation ->
@@ -357,8 +391,17 @@ class DesktopImmersiveController(
// Check if this is a direct immersive enter/exit transition.
if (transition == state?.transition) {
val state = requireState()
- val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId }
- .startAbsBounds
+ val immersiveChange = info.changes.firstOrNull { c ->
+ c.taskInfo?.taskId == state.taskId
+ }
+ if (immersiveChange == null) {
+ logV(
+ "Direct move for task#%d in %s direction missing immersive change.",
+ state.taskId, state.direction
+ )
+ return
+ }
+ val startBounds = immersiveChange.startAbsBounds
logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
when (state.direction) {
Direction.ENTER -> {
@@ -446,11 +489,30 @@ class DesktopImmersiveController(
private fun requireState(): TransitionState =
state ?: error("Expected non-null transition state")
+ private fun getRunningTransitions(): List<IBinder> {
+ val running = mutableListOf<IBinder>()
+ state?.let {
+ running.add(it.transition)
+ }
+ pendingExternalExitTransitions.forEach {
+ running.add(it.transition)
+ }
+ return running
+ }
+
private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
changes.any { c -> c.taskInfo?.taskId == taskId }
+ private fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopImmersiveController")
+ pw.println(innerPrefix + "state=" + state)
+ pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions)
+ }
+
/** The state of the currently running transition. */
- private data class TransitionState(
+ @VisibleForTesting
+ data class TransitionState(
val transition: IBinder,
val displayId: Int,
val taskId: Int,
@@ -483,7 +545,8 @@ class DesktopImmersiveController(
fun asExit(): Exit? = if (this is Exit) this else null
}
- private enum class Direction {
+ @VisibleForTesting
+ enum class Direction {
ENTER, EXIT
}
@@ -495,9 +558,10 @@ class DesktopImmersiveController(
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
- private companion object {
+ companion object {
private const val TAG = "DesktopImmersive"
- private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+ @VisibleForTesting
+ const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index cefcb757690f..01c680dc8325 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -205,11 +205,6 @@ class DesktopMixedTransitionHandler(
finishTransaction: SurfaceControl.Transaction,
finishCallback: TransitionFinishCallback,
): Boolean {
- val launchChange = findDesktopTaskChange(info, pending.launchingTask)
- if (launchChange == null) {
- logV("No launch Change, returning")
- return false
- }
// Check if there's also an immersive change during this launch.
val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
findDesktopTaskChange(info, exitingTask)
@@ -217,6 +212,13 @@ class DesktopMixedTransitionHandler(
val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
findDesktopTaskChange(info, minimizingTask)
}
+ val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+ if (launchChange == null) {
+ check(minimizeChange == null)
+ check(immersiveExitChange == null)
+ logV("No launch Change, returning")
+ return false
+ }
var subAnimationCount = -1
var combinedWct: WindowContainerTransaction? = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index fda709a4a2d7..08ca55f93e3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -102,6 +102,9 @@ class DesktopRepository (
/* Tracks last bounds of task before toggled to stable bounds. */
private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
+ /* Tracks last bounds of task before it is minimized. */
+ private val boundsBeforeMinimizeByTaskId = SparseArray<Rect>()
+
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
@@ -462,6 +465,14 @@ class DesktopRepository (
fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ /** Removes and returns the bounds saved before minimizing the given task. */
+ fun removeBoundsBeforeMinimize(taskId: Int): Rect? =
+ boundsBeforeMinimizeByTaskId.removeReturnOld(taskId)
+
+ /** Saves the bounds of the given task before minimizing. */
+ fun saveBoundsBeforeMinimize(taskId: Int, bounds: Rect?) =
+ boundsBeforeMinimizeByTaskId.set(taskId, Rect(bounds))
+
/** Removes and returns the bounds saved before entering immersive with the given task. */
fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? =
boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId)
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 162879c97a16..927fd88fb4ff 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
@@ -1770,9 +1770,13 @@ class DesktopTasksController(
transition: IBinder,
taskIdToMinimize: Int,
) {
- val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
+ val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
+ taskId = taskIdToMinimize
+ )
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f0e3a2bd8ffc..77af627a948a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -92,6 +92,12 @@ class DesktopTasksLimiter (
}
taskToMinimize.transitionInfo = info
activeTransitionTokensAndTasks[transition] = taskToMinimize
+
+ // Save current bounds before minimizing in case we need to restore to it later.
+ val boundsBeforeMinimize = info.changes.find { change ->
+ change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+ taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
+
this@DesktopTasksLimiter.minimizeTask(
taskToMinimize.displayId, taskToMinimize.taskId)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428f1693..0154d0455e50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -45,12 +45,17 @@ public interface Pip {
}
/**
- * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
+ * Set the callback when isInPip state is changed.
*
- * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
- * when it's changed.
+ * @param callback The callback accepts the state of isInPip when it's changed.
*/
- default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
+ default void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
+
+ /**
+ * Remove the callback when isInPip state is changed.
+ * @param callback The callback accepts the state of isInPip when it's changed.
+ */
+ default void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
/**
* Called when showing Pip menu.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c4e63dfdade9..86c826a680f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -67,6 +68,7 @@ import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -74,7 +76,9 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ScreenshotUtils;
@@ -87,6 +91,7 @@ import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -145,6 +150,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@Nullable private final PipPerfHintController mPipPerfHintController;
+ private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -388,6 +395,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -414,6 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mDesktopRepositoryOptional = desktopRepositoryOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -741,10 +752,23 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/** Returns the bounds to restore to when exiting PIP mode. */
+ // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
public Rect getExitDestinationBounds() {
+ if (isPipLaunchedInDesktopMode()) {
+ final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+ mTaskInfo.taskId);
+ return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+ }
return mPipBoundsState.getDisplayBounds();
}
+ /** Returns whether PiP was launched while in desktop mode. */
+ // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+ private boolean isPipLaunchedInDesktopMode() {
+ return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
+ && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+ }
+
private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
mTaskOrganizer.applyTransaction(wct);
@@ -1808,7 +1832,25 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* and can be overridden to restore to an alternate windowing mode.
*/
public int getOutPipWindowingMode() {
- // By default, simply reset the windowing mode to undefined.
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mTaskInfo.displayId);
+
+ // If PiP was launched while in desktop mode (we should return the task to freeform
+ // windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+ if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
+ final int displayWindowingMode =
+ tdaInfo.configuration.windowConfiguration.getWindowingMode();
+ if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
return WINDOWING_MODE_UNDEFINED;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 28b91c6cb812..8220ea5ea575 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -530,6 +530,13 @@ public class PipTransition extends PipTransitionController {
if (mFixedRotationState != FIXED_ROTATION_TRANSITION
&& mFinishTransaction != null) {
mFinishTransaction.merge(tx);
+ // Set window crop and position to destination bounds to avoid flickering.
+ if (hasValidLeash) {
+ mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
+ destinationBounds.height());
+ mFinishTransaction.setPosition(leash, destinationBounds.left,
+ destinationBounds.top);
+ }
}
} else {
wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 7f6118689dad..588b88753eb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -104,6 +104,7 @@ import com.android.wm.shell.sysui.UserChangeListener;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -215,7 +216,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private boolean mIsKeyguardShowingOrAnimating;
- private Consumer<Boolean> mOnIsInPipStateChangedListener;
+ private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
@VisibleForTesting
interface PipAnimationListener {
@@ -501,11 +502,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
false /* saveRestoreSnapFraction */);
});
mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> {
- if (mOnIsInPipStateChangedListener != null) {
- final boolean wasInPip = PipTransitionState.isInPip(oldState);
- final boolean nowInPip = PipTransitionState.isInPip(newState);
- if (nowInPip != wasInPip) {
- mOnIsInPipStateChangedListener.accept(nowInPip);
+ final boolean wasInPip = PipTransitionState.isInPip(oldState);
+ final boolean nowInPip = PipTransitionState.isInPip(newState);
+ if (nowInPip != wasInPip) {
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(nowInPip);
}
}
});
@@ -960,13 +961,19 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
}
- private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
- mOnIsInPipStateChangedListener = callback;
- if (mOnIsInPipStateChangedListener != null) {
+ private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.add(callback);
callback.accept(mPipTransitionState.isInPip());
}
}
+ private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.remove(callback);
+ }
+ }
+
private void setShelfHeightLocked(boolean visible, int height) {
final int shelfHeight = visible ? height : 0;
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
@@ -1222,9 +1229,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.addOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
- PipController.this.setOnIsInPipStateChangedListener(callback);
+ PipController.this.removeOnIsInPipStateChangedListener(callback);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 614ef2ab9831..fcba46108f67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -21,6 +21,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,6 +62,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -68,8 +70,9 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenOptional, pipPerfHintControllerOptional, Optional.empty(),
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
mTvPipTransition = tvPipTransition;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index d3f537b8f904..bc0918331168 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
@@ -66,6 +67,8 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -94,7 +97,7 @@ public class PipController implements ConfigurationChangeListener,
private final PipTouchHandler mPipTouchHandler;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
- private Consumer<Boolean> mOnIsInPipStateChangedListener;
+ private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
private PipAnimationListener mPipRecentsAnimationListener;
@@ -413,13 +416,13 @@ public class PipController implements ConfigurationChangeListener,
if (mPipTransitionState.isInSwipePipToHomeTransition()) {
mPipTransitionState.resetSwipePipToHomeState();
}
- if (mOnIsInPipStateChangedListener != null) {
- mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(true /* inPip */);
}
break;
case PipTransitionState.EXITED_PIP:
- if (mOnIsInPipStateChangedListener != null) {
- mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+ listener.accept(false /* inPip */);
}
break;
}
@@ -451,13 +454,19 @@ public class PipController implements ConfigurationChangeListener,
mPipTransitionState.dump(pw, innerPrefix);
}
- private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
- mOnIsInPipStateChangedListener = callback;
- if (mOnIsInPipStateChangedListener != null) {
+ private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.add(callback);
callback.accept(mPipTransitionState.isInPip());
}
}
+ private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ if (callback != null) {
+ mOnIsInPipStateChangedListeners.remove(callback);
+ }
+ }
+
private void setLauncherAppIconSize(int iconSizePx) {
mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
}
@@ -473,9 +482,16 @@ public class PipController implements ConfigurationChangeListener,
public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
@Override
- public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.addOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
- PipController.this.setOnIsInPipStateChangedListener(callback);
+ PipController.this.removeOnIsInPipStateChangedListener(callback);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ea783e9cadb6..3caad0966b1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -230,6 +231,11 @@ public class PipTransition extends PipTransitionController implements
// If there is no PiP change, exit this transition handler and potentially try others.
if (pipChange == null) return false;
+ // Other targets might have default transforms applied that are not relevant when
+ // playing PiP transitions, so reset those transforms if needed.
+ prepareOtherTargetTransforms(info, startTransaction, finishTransaction);
+
+ // Update the PipTransitionState while supplying the PiP leash and token to be cached.
Bundle extra = new Bundle();
extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
@@ -341,17 +347,21 @@ public class PipTransition extends PipTransitionController implements
(destinationBounds.height() - overlaySize) / 2f);
}
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = mPipDisplayLayoutState.getRotation();
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange);
+ pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint);
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, destinationBounds, delta);
@@ -387,55 +397,36 @@ public class PipTransition extends PipTransitionController implements
return false;
}
+ final SurfaceControl pipLeash = getLeash(pipChange);
final Rect startBounds = pipChange.getStartAbsBounds();
final Rect endBounds = pipChange.getEndAbsBounds();
-
final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
- final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
- final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
- endBounds);
- final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
- : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
-
- final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
-
- // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
- // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
- // by the Transitions framework to simplify Task opening transitions.
- if (TransitionUtil.isOpeningType(info.getType())) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getLeash() == null) continue;
- if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- startTransaction.setAlpha(change.getLeash(), 1f);
- }
- }
- }
-
- final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
+ final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
+ pipActivityChange);
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
- fixedRotationChange.getEndFixedRotation());
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, delta);
- if (sourceRectHint == null) {
- // update the src-rect-hint in params in place, to set up initial animator transform.
- params.getSourceRectHint().set(adjustedSourceRectHint);
+ if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) {
+ // If app provided src-rect-hint is invalid, use app icon overlay.
animator.setAppIconContentOverlay(
mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ params.getSourceRectHint().set(adjustedSourceRectHint);
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
animator.setAnimationEndCallback(() -> {
if (animator.getContentOverlayLeash() != null) {
@@ -457,11 +448,22 @@ public class PipTransition extends PipTransitionController implements
animator.start();
}
- private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
- TransitionInfo.Change pipActivityChange, int endRotation) {
- final Rect endBounds = pipTaskChange.getEndAbsBounds();
- final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
- int startRotation = pipTaskChange.getStartRotation();
+ private void handleBoundsEnterFixedRotation(TransitionInfo info,
+ TransitionInfo.Change outPipTaskChange,
+ TransitionInfo.Change outPipActivityChange) {
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+ final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds();
+ int startRotation = outPipTaskChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+
+ if (startRotation == endRotation) {
+ return;
+ }
+
+ // This is used by display change listeners to respond properly to fixed rotation.
+ mPipTransitionState.setInFixedRotation(true);
// Cache the task to activity offset to potentially restore later.
Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left,
@@ -490,15 +492,15 @@ public class PipTransition extends PipTransitionController implements
endBounds.top + activityEndOffset.y);
}
- private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
- final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
final int width = endBounds.width();
final int height = endBounds.height();
final int left = endBounds.left;
final int top = endBounds.top;
int newTop, newLeft;
- if (endRotation == Surface.ROTATION_90) {
+ if (delta == Surface.ROTATION_90) {
newLeft = top;
newTop = -(left + width);
} else {
@@ -585,15 +587,11 @@ public class PipTransition extends PipTransitionController implements
final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
startBounds);
- final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : endRotation - startRotation;
-
+ // We define delta = startRotation - endRotation, so we need to flip the sign.
+ final int delta = -getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- handleExpandFixedRotation(pipChange, endRotation);
+ // Update PiP target change in place to prepare for fixed rotation;
+ handleExpandFixedRotation(pipChange, delta);
}
PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
@@ -661,6 +659,72 @@ public class PipTransition extends PipTransitionController implements
return null;
}
+ @NonNull
+ private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipTaskChange,
+ @NonNull TransitionInfo.Change pipActivityChange) {
+ final Rect startBounds = pipTaskChange.getStartAbsBounds();
+ final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams;
+
+ // Get the source-rect-hint provided by the app and check its validity; null if invalid.
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
+ endBounds);
+
+ final Rect adjustedSourceRectHint = new Rect();
+ if (sourceRectHint != null) {
+ adjustedSourceRectHint.set(sourceRectHint);
+ // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts;
+ // then, offset the valid app provided source rect hint by the cutout insets.
+ // For single-activity PiP, just use the pinned task to get the cutouts instead.
+ TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null
+ ? getChangeByToken(info, pipActivityChange.getLastParent()) : null;
+ Rect cutoutInsets = parentBeforePip != null
+ ? parentBeforePip.getTaskInfo().displayCutoutInsets
+ : pipTaskChange.getTaskInfo().displayCutoutInsets;
+ if (cutoutInsets != null
+ && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
+ adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
+ }
+ } else {
+ // For non-valid app provided src-rect-hint, calculate one to crop into during
+ // app icon overlay animation.
+ float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
+ adjustedSourceRectHint.set(
+ PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio));
+ }
+ return adjustedSourceRectHint;
+ }
+
+ @Surface.Rotation
+ private int getFixedRotationDelta(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipChange) {
+ TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ int startRotation = pipChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+ int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : startRotation - endRotation;
+ return delta;
+ }
+
+ private void prepareOtherTargetTransforms(TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
+ // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
+ // by the Transitions framework to simplify Task opening transitions.
+ if (TransitionUtil.isOpeningType(info.getType())) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getLeash() == null) continue;
+ if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+ startTransaction.setAlpha(change.getLeash(), 1f);
+ }
+ }
+ }
+
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 40065b9287a6..9016c45e8197 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -34,6 +34,8 @@ import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -67,6 +69,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
@@ -216,8 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
break;
}
}
- final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
- mixer == null ? this : mixer);
+ final int transitionType = Flags.enableShellTopTaskTracking()
+ ? TRANSIT_START_RECENTS_TRANSITION
+ : TRANSIT_TO_FRONT;
+ final IBinder transition = mTransitions.startTransition(transitionType,
+ wct, mixer == null ? this : mixer);
if (mixer != null) {
setTransitionForMixer.accept(transition);
}
@@ -300,7 +306,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- controller.merge(info, t, finishCallback);
+ controller.merge(info, t, mergeTarget, finishCallback);
}
@Override
@@ -367,6 +373,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
private boolean mPausingSeparateHome = false;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
private PictureInPictureSurfaceTransaction mPipTransaction = null;
+ // This is the transition that backs the entire recents transition, and the one that the
+ // pending finish transition below will be merged into
private IBinder mTransition = null;
private boolean mKeyguardLocked = false;
private boolean mWillFinishToHome = false;
@@ -386,6 +394,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// next called.
private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
+ // Used to track a pending finish transition
+ private IBinder mPendingFinishTransition;
+ private IResultReceiver mPendingRunnerFinishCb;
+
RecentsController(IRecentsAnimationRunner listener) {
mInstanceId = System.identityHashCode(this);
mListener = listener;
@@ -523,6 +535,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mInfo = null;
mTransition = null;
mPendingPauseSnapshotsForCancel = null;
+ mPipTaskId = -1;
+ mPipTask = null;
+ mPipTransaction = null;
+ mPendingRunnerFinishCb = null;
+ mPendingFinishTransition = null;
mControllers.remove(this);
for (int i = 0; i < mStateListeners.size(); i++) {
mStateListeners.get(i).onAnimationStateChanged(false);
@@ -734,6 +751,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
// the pausing apps.
t.setLayer(target.leash, layer);
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " not handling home taskId=%d", taskInfo.taskId);
// do nothing
} else if (TransitionUtil.isOpeningType(change.getMode())) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -872,16 +891,35 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
}
+ /**
+ * Note: because we use a book-end transition to finish the recents transition, we must
+ * either always merge the incoming transition, or always cancel the recents transition
+ * if we don't handle the incoming transition to ensure that the end transition is queued
+ * before any unhandled transitions.
+ */
@SuppressLint("NewApi")
- void merge(TransitionInfo info, SurfaceControl.Transaction t,
+ void merge(TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
if (mFinishCB == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: skip, no finish callback",
mInstanceId);
- // This was no-op'd (likely a repeated start) and we've already sent finish.
+ // This was no-op'd (likely a repeated start) and we've already completed finish.
+ return;
+ }
+
+ if (Flags.enableShellTopTaskTracking()
+ && info.getType() == TRANSIT_END_RECENTS_TRANSITION
+ && mergeTarget == mTransition) {
+ // This is a pending finish, so merge the end transition to trigger completing the
+ // cleanup of the recents transition
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.merge: TRANSIT_END_RECENTS_TRANSITION",
+ mInstanceId);
+ finishCallback.onTransitionFinished(null /* wct */);
return;
}
+
if (info.getType() == TRANSIT_SLEEP) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: transit_sleep", mInstanceId);
@@ -1245,7 +1283,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
return;
}
- if (mFinishCB == null) {
+ if (mFinishCB == null
+ || (Flags.enableShellTopTaskTracking() && mPendingFinishTransition != null)) {
Slog.e(TAG, "Duplicate call to finish");
return;
}
@@ -1254,19 +1293,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
&& !mWillFinishToHome
&& mPausingTasks != null
&& mState == STATE_NORMAL;
- if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
- mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
- } else if (!toHome) {
- // For some transitions, we may have notified home activity that it became visible.
- // We need to notify the observer that we are no longer going home.
- mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ if (!Flags.enableShellTopTaskTracking()) {
+ // This is only necessary when the recents transition is finished using a finishWCT,
+ // otherwise a new transition will notify the relevant observers
+ if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+ } else if (!toHome) {
+ // For some transitions, we may have notified home activity that it became
+ // visible. We need to notify the observer that we are no longer going home.
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false);
+ }
}
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.finishInner: toHome=%b userLeave=%b "
- + "willFinishToHome=%b state=%d",
- mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState);
- final Transitions.TransitionFinishCallback finishCB = mFinishCB;
- mFinishCB = null;
+ + "willFinishToHome=%b state=%d reason=%s",
+ mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome, mState, reason);
final SurfaceControl.Transaction t = mFinishTransaction;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1328,6 +1370,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
for (int i = 0; i < mClosingTasks.size(); ++i) {
cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
}
+
if (mPipTransaction != null && sendUserLeaveHint) {
SurfaceControl pipLeash = null;
TransitionInfo.Change pipChange = null;
@@ -1379,15 +1422,50 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
mTransitions.startTransition(TRANSIT_PIP, wct, null /* handler */);
// We need to clear the WCT to send finishWCT=null for Recents.
wct.clear();
+
+ if (Flags.enableShellTopTaskTracking()) {
+ // In this case, we've already started the PIP transition, so we can
+ // clean up immediately
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(null);
+ return;
+ }
}
}
- mPipTaskId = -1;
- mPipTask = null;
- mPipTransaction = null;
}
}
+
+ if (Flags.enableShellTopTaskTracking()) {
+ if (!wct.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: "
+ + "Queuing TRANSIT_END_RECENTS_TRANSITION", mInstanceId);
+ mPendingRunnerFinishCb = runnerFinishCb;
+ mPendingFinishTransition = mTransitions.startTransition(
+ TRANSIT_END_RECENTS_TRANSITION, wct,
+ new PendingFinishTransitionHandler());
+ } else {
+ // If there's no work to do, just go ahead and clean up
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(null /* wct */);
+ }
+ } else {
+ mPendingRunnerFinishCb = runnerFinishCb;
+ onFinishInner(wct);
+ }
+ }
+
+ /**
+ * Runs the actual logic to finish the recents transition.
+ */
+ private void onFinishInner(@Nullable WindowContainerTransaction wct) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.finishInner: Completing finish", mInstanceId);
+ final Transitions.TransitionFinishCallback finishCb = mFinishCB;
+ final IResultReceiver runnerFinishCb = mPendingRunnerFinishCb;
+
cleanUp();
- finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
+ finishCb.onTransitionFinished(wct);
if (runnerFinishCb != null) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -1472,6 +1550,40 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
});
}
+
+ /**
+ * A temporary transition handler used with the pending finish transition, which runs the
+ * cleanup/finish logic once the pending transition is merged/handled.
+ * This is only initialized if Flags.enableShellTopTaskTracking() is enabled.
+ */
+ private class PendingFinishTransitionHandler implements Transitions.TransitionHandler {
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ // Once we have merged (or not if the WCT didn't result in any changes), then we can
+ // run the pending finish logic
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.onTransitionConsumed: "
+ + "Consumed pending finish transition", mInstanceId);
+ onFinishInner(null /* wct */);
+ }
+ };
};
/** Utility class to track the state of a task as-seen by recents. */
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 cc0e1df115c2..19a73f3631f2 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
@@ -55,7 +55,6 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
-import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1099,16 +1098,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
- setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
- }
-
- private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
- @Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
- if (mSideStage.mVisible && updateBounds) {
+ if (mSideStage.mVisible) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
onLayoutSizeChanged(mSplitLayout);
@@ -1199,6 +1193,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!isSplitActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
@@ -1598,6 +1593,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (present) {
updateRecentTasksSplitPair();
+ } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
+ mRecentTasks.ifPresent(recentTasks -> {
+ // remove the split pair mapping from recentTasks, and disable further updates
+ // to splits in the recents until we enter split again.
+ recentTasks.removeSplitPair(taskId);
+ });
+ exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 1d456aed5f4d..3f191497e1ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -203,6 +203,12 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition type to minimize a task. */
public static final int TRANSIT_MINIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 20;
+ /** Transition to start the recents transition */
+ public static final int TRANSIT_START_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 21;
+
+ /** Transition to end the recents transition */
+ public static final int TRANSIT_END_RECENTS_TRANSITION = TRANSIT_FIRST_CUSTOM + 22;
+
/** Transition type for desktop mode transitions. */
public static final int TRANSIT_DESKTOP_MODE_TYPES =
WindowManager.TRANSIT_FIRST_CUSTOM + 100;
@@ -1875,6 +1881,8 @@ public class Transitions implements RemoteCallable<Transitions>,
case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
case TRANSIT_MINIMIZE -> "MINIMIZE";
+ case TRANSIT_START_RECENTS_TRANSITION -> "START_RECENTS_TRANSITION";
+ case TRANSIT_END_RECENTS_TRANSITION -> "END_RECENTS_TRANSITION";
default -> "";
};
return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
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 f2d8a782de34..5e3026a73971 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
@@ -465,7 +465,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
if (taskInfo.displayId != oldTaskInfo.displayId
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
removeTaskFromEventReceiver(oldTaskInfo.displayId);
incrementEventReceiverTasks(taskInfo.displayId);
}
@@ -539,7 +539,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.close();
final int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
removeTaskFromEventReceiver(displayId);
}
// Remove the decoration from the cache last because WindowDecoration#close could still
@@ -1234,7 +1234,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
relevantDecor.updateHoverAndPressStatus(ev);
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mTransitionDragActive && !Flags.enableHandleInputFix()) {
+ if (!mTransitionDragActive && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
relevantDecor.closeHandleMenuIfNeeded(ev);
}
}
@@ -1277,7 +1277,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
final boolean shouldStartTransitionDrag =
relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
- || Flags.enableHandleInputFix();
+ || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
mTransitionDragActive = true;
}
@@ -1593,7 +1593,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
mFocusTransitionObserver.hasGlobalFocus(taskInfo));
- if (!Flags.enableHandleInputFix()) {
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
}
@@ -1754,7 +1754,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& Flags.enableDesktopWindowingImmersiveHandleHiding()) {
decor.onInsetsStateChanged(insetsState);
}
- if (!Flags.enableHandleInputFix()) {
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// If status bar inset is visible, top task is not in immersive mode.
// This value is only needed when the App Handle input is being handled
// through the global input monitor (hence the flag check) to ignore gestures
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 d97632a9428c..d3772325004d 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
@@ -807,7 +807,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void disposeStatusBarInputLayer() {
if (!isAppHandle(mWindowDecorViewHolder)
- || !Flags.enableHandleInputFix()) {
+ || !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
return;
}
asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
@@ -951,7 +951,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
- } else if (isAppHandle && !Flags.enableHandleInputFix()) {
+ } else if (isAppHandle && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// The focused decor (fullscreen/split) does not need to handle input because input in
// the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
// Note: This does not apply with the above flag enabled as the status bar input layer
@@ -1560,13 +1560,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
- || Flags.enableHandleInputFix()) {
+ || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
return false;
}
// The status bar input layer can only receive input in handle coordinates to begin with,
// so checking coordinates is unnecessary as input is always within handle bounds.
if (isAppHandle(mWindowDecorViewHolder)
- && Flags.enableHandleInputFix()
+ && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()
&& isCaptionVisible()) {
return true;
}
@@ -1603,7 +1603,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare
*/
void checkTouchEvent(MotionEvent ev) {
- if (mResult.mRootView == null || Flags.enableHandleInputFix()) return;
+ if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
final View handle = caption.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
@@ -1616,7 +1616,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH.
// This is for the case that some of the handle menu is underneath the status bar.
if (isAppHandle(mWindowDecorViewHolder)
- && !Flags.enableHandleInputFix()) {
+ && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
mHandleMenu.checkMotionEvent(ev);
closeHandleMenuIfNeeded(ev);
}
@@ -1630,7 +1630,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* @param ev the MotionEvent to compare against.
*/
void updateHoverAndPressStatus(MotionEvent ev) {
- if (mResult.mRootView == null || Flags.enableHandleInputFix()) return;
+ if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
final boolean inHandle = !isHandleMenuActive()
&& checkTouchEventInFocusedCaptionHandle(ev);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 2edc380756ac..2da20bd19c0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -39,11 +39,11 @@ import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
+import android.window.DesktopModeFlags
import android.window.SurfaceSyncGroup
import androidx.annotation.VisibleForTesting
import androidx.compose.ui.graphics.toArgb
import androidx.core.view.isGone
-import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -83,7 +83,7 @@ class HandleMenu(
private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo
private val isViewAboveStatusBar: Boolean
- get() = (Flags.enableHandleInputFix() && !taskInfo.isFreeform)
+ get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform)
private val pillElevation: Int = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_pill_elevation)
@@ -201,7 +201,8 @@ class HandleMenu(
val x = handleMenuPosition.x.toInt()
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
- if ((!taskInfo.isFreeform && Flags.enableHandleInputFix()) || forceShowSystemBars) {
+ if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue())
+ || forceShowSystemBars) {
AdditionalSystemViewContainer(
windowManagerWrapper = windowManagerWrapper,
taskId = taskInfo.taskId,
@@ -237,7 +238,7 @@ class HandleMenu(
menuX = marginMenuStart
menuY = captionY + marginMenuTop
} else {
- if (Flags.enableHandleInputFix()) {
+ if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
// In a focused decor, we use global coordinates for handle menu. Therefore we
// need to account for other factors like split stage and menu/handle width to
// center the menu.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
index cf82bb4f9919..8bc56e0807a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt
@@ -16,13 +16,12 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
-import com.android.window.flags.Flags
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ImageButton
+import android.window.DesktopModeFlags
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
/**
* A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers.
@@ -39,7 +38,7 @@ class HandleMenuImageButton(
lateinit var taskInfo: RunningTaskInfo
override fun onHoverEvent(motionEvent: MotionEvent): Boolean {
- if (Flags.enableHandleInputFix() || taskInfo.isFreeform) {
+ if (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() || taskInfo.isFreeform) {
return super.onHoverEvent(motionEvent)
} else {
return false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index b5700ffb046b..503ad92d4d71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -34,15 +34,14 @@ import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
+import android.window.DesktopModeFlags
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import com.android.internal.policy.SystemBarUtils
-import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data
/**
* A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
@@ -141,7 +140,7 @@ internal class AppHandleViewHolder(
private fun createStatusBarInputLayer(handlePosition: Point,
handleWidth: Int,
handleHeight: Int) {
- if (!Flags.enableHandleInputFix()) return
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return
statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
index d38b848fbb4d..329a10998f23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -16,9 +16,8 @@
package com.android.wm.shell.bubbles.bar;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
-import android.graphics.drawable.ColorDrawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -47,10 +46,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_lightBg() {
mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
}
@@ -58,10 +56,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_darkBg() {
mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
new file mode 100644
index 000000000000..b9490b881d08
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.common.pip;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipAppOpsListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipAppOpsListenerTest {
+
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPackageManager;
+ @Mock private AppOpsManager mMockAppOpsManager;
+ @Mock private PipAppOpsListener.Callback mMockCallback;
+ @Mock private ShellExecutor mMockExecutor;
+
+ private PipAppOpsListener mPipAppOpsListener;
+
+ private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mOnOpChangedListenerCaptor;
+ private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+ private Pair<ComponentName, Integer> mTopPipActivity;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+ .thenReturn(mMockAppOpsManager);
+ mOnOpChangedListenerCaptor = ArgumentCaptor.forClass(
+ AppOpsManager.OnOpChangedListener.class);
+ mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ }
+
+ @Test
+ public void onActivityPinned_registerAppOpsListener() {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityPinned(packageName);
+
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void onActivityUnpinned_unregisterAppOpsListener() {
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityUnpinned();
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void disablePipAppOps_dismissPip() throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockExecutor).execute(mRunnableArgumentCaptor.capture());
+ Runnable runnable = mRunnableArgumentCaptor.getValue();
+ runnable.run();
+ verify(mMockCallback).dismissPip();
+ }
+
+ @Test
+ public void disablePipAppOps_differentPackage_doNothing()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName + ".modified";
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verifyZeroInteractions(mMockExecutor);
+ }
+
+ @Test
+ public void disablePipAppOps_nameNotFound_unregisterAppOpsListener()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ private Pair<ComponentName, Integer> getTopPipActivity(Context context) {
+ return mTopPipActivity;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index e05a0b54fcf4..a4f4d05d2079 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
import android.graphics.Rect
@@ -24,6 +25,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.Display.DEFAULT_DISPLAY
import android.view.Surface
import android.view.SurfaceControl
@@ -43,6 +45,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -64,17 +67,19 @@ import org.mockito.kotlin.whenever
* Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest
*/
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DesktopImmersiveControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var mockTransitions: Transitions
private lateinit var desktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayLayout: DisplayLayout
- private val transactionSupplier = { SurfaceControl.Transaction() }
+ private val transactionSupplier = { StubTransaction() }
private lateinit var controller: DesktopImmersiveController
@@ -89,10 +94,12 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
(invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
}
controller = DesktopImmersiveController(
+ shellInit = mock(),
transitions = mockTransitions,
desktopRepository = desktopRepository,
displayController = mockDisplayController,
shellTaskOrganizer = mockShellTaskOrganizer,
+ shellCommandHandler = mock(),
transactionSupplier = transactionSupplier,
)
}
@@ -672,6 +679,60 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
assertThat(controller.isImmersiveChange(transition, change)).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ controller.moveTaskToNonImmersive(task)
+
+ controller.animateResizeChange(
+ change = TransitionInfo.Change(task.token, SurfaceControl()).apply {
+ taskInfo = task
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = { }
+ )
+ animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
+
+ assertThat(controller.state).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAnimation_missingChange_clearsState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ controller.moveTaskToImmersive(task)
+
+ controller.startAnimation(
+ transition = mockBinder,
+ info = createTransitionInfo(changes = emptyList()),
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = {}
+ )
+
+ assertThat(controller.state).isNull()
+ }
+
private fun createTransitionInfo(
@TransitionType type: Int = TRANSIT_CHANGE,
@TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 414c1a658b95..7f790d574a7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -936,6 +936,28 @@ class DesktopRepositoryTest : ShellTestCase() {
}
@Test
+ fun saveBoundsBeforeMinimize_boundsSavedByTaskId() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+
+ assertThat(repo.removeBoundsBeforeMinimize(taskId)).isEqualTo(bounds)
+ }
+
+ @Test
+ fun removeBoundsBeforeMinimize_returnsNullAfterBoundsRemoved() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+ repo.removeBoundsBeforeMinimize(taskId)
+
+ val boundsBeforeMinimize = repo.removeBoundsBeforeMinimize(taskId)
+
+ assertThat(boundsBeforeMinimize).isNull()
+ }
+
+ @Test
fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 01b69aed8465..456b50da095b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
import android.os.Binder
import android.os.Handler
import android.platform.test.annotations.DisableFlags
@@ -24,8 +25,10 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -63,6 +66,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.kotlin.eq
@@ -235,6 +239,30 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
@Test
+ fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ val bounds = Rect(0, 0, 200, 200)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ desktopTasksLimiter.addPendingMinimizeChange(
+ transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+
+ val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+ mode = TRANSIT_TO_BACK
+ taskInfo = task
+ setStartAbsBounds(bounds)
+ }
+ desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ transition,
+ TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+ StubTransaction() /* startTransaction */,
+ StubTransaction() /* finishTransaction */)
+
+ assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
+ assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo(
+ bounds)
+ }
+
+ @Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
val mergedTransition = Binder()
val newTransition = Binder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index bcb7461bfae7..5f58265b45f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -47,6 +47,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.MockSurfaceControlHelper;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -61,6 +62,7 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -90,6 +92,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
+ @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
private TestShellExecutor mMainExecutor;
@@ -120,8 +124,10 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
- Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
- mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
+ Optional.empty() /* pipPerfHintControllerOptional */,
+ mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+ mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
+ mMainExecutor);
mMainExecutor.flushAll();
preparePipTaskOrg();
preparePipSurfaceTransactionHelper();
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
index c6cee07d1946..5ab5a7a59c2a 100644
--- a/libs/appfunctions/Android.bp
+++ b/libs/appfunctions/Android.bp
@@ -18,10 +18,10 @@ package {
}
java_sdk_library {
- name: "com.google.android.appfunctions.sidecar",
+ name: "com.android.extensions.appfunctions",
owner: "google",
srcs: ["java/**/*.java"],
- api_packages: ["com.google.android.appfunctions.sidecar"],
+ api_packages: ["com.android.extensions.appfunctions"],
dex_preopt: {
enabled: false,
},
@@ -31,9 +31,9 @@ java_sdk_library {
}
prebuilt_etc {
- name: "appfunctions.sidecar.xml",
+ name: "appfunctions.extension.xml",
system_ext_specific: true,
sub_dir: "permissions",
- src: "appfunctions.sidecar.xml",
+ src: "appfunctions.extension.xml",
filename_from_src: true,
}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index faf84a8ab5ac..de402095e195 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -1,9 +1,29 @@
// Signature format: 2.0
-package com.google.android.appfunctions.sidecar {
+package com.android.extensions.appfunctions {
+
+ public final class AppFunctionException extends java.lang.Exception {
+ ctor public AppFunctionException(int, @Nullable String);
+ ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+ method public int getErrorCategory();
+ method public int getErrorCode();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+ field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+ field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+ field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+ field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+ field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_DENIED = 1000; // 0x3e8
+ field public static final int ERROR_DISABLED = 1002; // 0x3ea
+ field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+ field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+ field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+ }
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -15,7 +35,7 @@ package com.google.android.appfunctions.sidecar {
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -29,33 +49,17 @@ package com.google.android.appfunctions.sidecar {
public static final class ExecuteAppFunctionRequest.Builder {
ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
- method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build();
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
}
public final class ExecuteAppFunctionResponse {
- method public int getErrorCategory();
- method @Nullable public String getErrorMessage();
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method @NonNull public android.os.Bundle getExtras();
- method public int getResultCode();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- method public boolean isSuccess();
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
- method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
- field public static final int ERROR_CATEGORY_APP = 3; // 0x3
- field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
- field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
- field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
- field public static final String PROPERTY_RETURN_VALUE = "returnValue";
- field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
- field public static final int RESULT_CANCELLED = 2001; // 0x7d1
- field public static final int RESULT_DENIED = 1000; // 0x3e8
- field public static final int RESULT_DISABLED = 1002; // 0x3ea
- field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
- field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
- field public static final int RESULT_OK = 0; // 0x0
- field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
+ field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
}
}
diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml
index bef8b6ec7ce6..dd09cc39d12f 100644
--- a/libs/appfunctions/appfunctions.sidecar.xml
+++ b/libs/appfunctions/appfunctions.extension.xml
@@ -16,6 +16,6 @@
-->
<permissions>
<library
- name="com.google.android.appfunctions.sidecar"
- file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/>
+ name="com.android.extensions.appfunctions"
+ file="/system_ext/framework/com.android.extensions.appfunctions.jar"/>
</permissions> \ No newline at end of file
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
new file mode 100644
index 000000000000..28c3b3df9b1c
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -0,0 +1,211 @@
+/*
+ * 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.extensions.appfunctions;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Represents an app function related errors. */
+public final class AppFunctionException extends Exception {
+ /**
+ * The caller does not have the permission to execute an app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DENIED = 1000;
+
+ /**
+ * The caller supplied invalid arguments to the execution request.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+ /**
+ * The caller tried to execute a disabled app function.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_DISABLED = 1002;
+
+ /**
+ * The caller tried to execute a function that does not exist.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+ */
+ public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+ /**
+ * An internal unexpected error coming from the system.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_SYSTEM_ERROR = 2000;
+
+ /**
+ * The operation was cancelled. Use this error code to report that a cancellation is done after
+ * receiving a cancellation signal.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+ */
+ public static final int ERROR_CANCELLED = 2001;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ *
+ * <p>This error is thrown when the service is connected in the remote application but an
+ * unexpected error is thrown from the bound application.
+ *
+ * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+ */
+ public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+ /**
+ * The error category is unknown.
+ *
+ * <p>This is the default value for {@link #getErrorCategory}.
+ */
+ public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * The error is caused by the app requesting a function execution.
+ *
+ * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+ * invalid function ID.
+ *
+ * <p>Errors in the category fall in the range 1000-1999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+ /**
+ * The error is caused by an issue in the system.
+ *
+ * <p>For example, the AppFunctionService implementation is not found by the system.
+ *
+ * <p>Errors in the category fall in the range 2000-2999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+ /**
+ * The error is caused by the app providing the function.
+ *
+ * <p>For example, the app crashed when the system is executing the request.
+ *
+ * <p>Errors in the category fall in the range 3000-3999 inclusive.
+ */
+ public static final int ERROR_CATEGORY_APP = 3;
+
+ private final int mErrorCode;
+ @Nullable private final String mErrorMessage;
+ @NonNull private final Bundle mExtras;
+
+ public AppFunctionException(int errorCode, @Nullable String errorMessage) {
+ this(errorCode, errorMessage, Bundle.EMPTY);
+ }
+
+ public AppFunctionException(
+ int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ mExtras = extras;
+ }
+
+ /** Returns one of the {@code ERROR} constants. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Returns the error category.
+ *
+ * <p>This method categorizes errors based on their underlying cause, allowing developers to
+ * implement targeted error handling and provide more informative error messages to users. It
+ * maps ranges of error codes to specific error categories.
+ *
+ * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+ * any error category.
+ *
+ * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+ * error code ranges.
+ */
+ @ErrorCategory
+ public int getErrorCategory() {
+ if (mErrorCode >= 1000 && mErrorCode < 2000) {
+ return ERROR_CATEGORY_REQUEST_ERROR;
+ }
+ if (mErrorCode >= 2000 && mErrorCode < 3000) {
+ return ERROR_CATEGORY_SYSTEM;
+ }
+ if (mErrorCode >= 3000 && mErrorCode < 4000) {
+ return ERROR_CATEGORY_APP;
+ }
+ return ERROR_CATEGORY_UNKNOWN;
+ }
+
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Error codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_"},
+ value = {
+ ERROR_DENIED,
+ ERROR_APP_UNKNOWN_ERROR,
+ ERROR_FUNCTION_NOT_FOUND,
+ ERROR_SYSTEM_ERROR,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_DISABLED,
+ ERROR_CANCELLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ /**
+ * Error categories.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"ERROR_CATEGORY_"},
+ value = {
+ ERROR_CATEGORY_UNKNOWN,
+ ERROR_CATEGORY_REQUEST_ERROR,
+ ERROR_CATEGORY_APP,
+ ERROR_CATEGORY_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCategory {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 2075104ff868..9eb66a33fedc 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.Manifest;
import android.annotation.CallbackExecutor;
@@ -31,7 +31,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
/**
* Provides app functions related functionalities.
@@ -115,7 +114,9 @@ public final class AppFunctionManager {
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback) {
Objects.requireNonNull(sidecarRequest);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -126,10 +127,20 @@ public final class AppFunctionManager {
platformRequest,
executor,
cancellationSignal,
- (platformResponse) -> {
- callback.accept(
- SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(
+ android.app.appfunctions.ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ SidecarConverter.getSidecarExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(
+ android.app.appfunctions.AppFunctionException exception) {
+ callback.onError(
+ SidecarConverter.getSidecarAppFunctionException(exception));
+ }
});
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 0dc87e45b7e3..55f579138218 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
-import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -26,9 +27,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
-import android.util.Log;
-
-import java.util.function.Consumer;
+import android.os.OutcomeReceiver;
/**
* Abstract base class to provide app functions to the system.
@@ -80,10 +79,18 @@ public abstract class AppFunctionService extends Service {
platformRequest),
callingPackage,
cancellationSignal,
- (sidecarResponse) -> {
- callback.accept(
- SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ callback.onResult(
+ getPlatformExecuteAppFunctionResponse(result));
+ }
+
+ @Override
+ public void onError(AppFunctionException exception) {
+ callback.onError(
+ getPlatformAppFunctionException(exception));
+ }
});
});
@@ -116,12 +123,14 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
+ * @param callback A callback to report back the result or error.
*/
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ @NonNull
+ OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+ callback);
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
index 593c5213dd52..baddc245f0f1 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
@@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest {
* Returns the function parameters. The key is the parameter name, and the value is the
* parameter value.
*
- * <p>The bundle may have missing parameters. Developers are advised to implement defensive
- * handling measures.
+ * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+ * implement defensive handling measures.
*
* <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
* obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 000000000000..0826f04a50dd
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.extensions.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/** The response to an app function execution. */
+public final class ExecuteAppFunctionResponse {
+ /**
+ * The name of the property that stores the function return value within the {@code
+ * resultDocument}.
+ *
+ * <p>See {@link GenericDocument#getProperty(String)} for more information.
+ *
+ * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
+ * be empty {@link GenericDocument}.
+ *
+ * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+ * return {@code null}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
+
+ /**
+ * Returns the return value of the executed function.
+ *
+ * <p>The return value is stored in a {@link GenericDocument} with the key {@link
+ * #PROPERTY_RETURN_VALUE}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ @NonNull private final GenericDocument mResultDocument;
+
+ /** Returns the additional metadata data relevant to this function execution response. */
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * @param resultDocument The return value of the executed function.
+ */
+ public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+ this(resultDocument, Bundle.EMPTY);
+ }
+
+ /**
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata for this function execution response.
+ */
+ public ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
+ }
+
+ /**
+ * Returns a generic document containing the return value of the executed function.
+ *
+ * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
+ *
+ * <p>Sample code for extracting the return value:
+ *
+ * <pre>
+ * GenericDocument resultDocument = response.getResultDocument();
+ * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+ * if (returnValue != null) {
+ * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+ * // {@link GenericDocument#getPropertyLong} etc.
+ * // Do something with the returnValue
+ * }
+ * </pre>
+ *
+ * @see AppFunctionManager on how to determine the expected function return.
+ */
+ @NonNull
+ public GenericDocument getResultDocument() {
+ return mResultDocument;
+ }
+
+ /** Returns the additional metadata for this function execution response. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
index b1b05f79f33f..5e1fc7e684e2 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
import android.annotation.NonNull;
@@ -28,46 +28,50 @@ public final class SidecarConverter {
private SidecarConverter() {}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionRequest}
*
* @hide
*/
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionRequest
getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
- return new
- android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
- * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link
+ * android.app.appfunctions.ExecuteAppFunctionResponse}
*
* @hide
*/
@NonNull
public static android.app.appfunctions.ExecuteAppFunctionResponse
getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
- }
+ return new android.app.appfunctions.ExecuteAppFunctionResponse(
+ response.getResultDocument(), response.getExtras());
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * Converts sidecar's {@link AppFunctionException} into platform's {@link
+ * android.app.appfunctions.AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.AppFunctionException
+ getPlatformAppFunctionException(@NonNull AppFunctionException exception) {
+ return new android.app.appfunctions.AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's
+ * {@link ExecuteAppFunctionRequest}
*
* @hide
*/
@@ -75,30 +79,34 @@ public final class SidecarConverter {
public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
@NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
return new ExecuteAppFunctionRequest.Builder(
- request.getTargetPackageName(),
- request.getFunctionIdentifier())
+ request.getTargetPackageName(), request.getFunctionIdentifier())
.setExtras(request.getExtras())
.setParameters(request.getParameters())
.build();
}
/**
- * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
- * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into
+ * sidecar's {@link ExecuteAppFunctionResponse}
*
* @hide
*/
@NonNull
public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
@NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
- if (response.isSuccess()) {
- return ExecuteAppFunctionResponse.newSuccess(
- response.getResultDocument(), response.getExtras());
- } else {
- return ExecuteAppFunctionResponse.newFailure(
- response.getResultCode(),
- response.getErrorMessage(),
- response.getExtras());
- }
+ return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras());
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.AppFunctionException} into
+ * sidecar's {@link AppFunctionException}
+ *
+ * @hide
+ */
+ @NonNull
+ public static AppFunctionException getSidecarAppFunctionException(
+ @NonNull android.app.appfunctions.AppFunctionException exception) {
+ return new AppFunctionException(
+ exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
}
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
deleted file mode 100644
index 4e88fb025a9d..000000000000
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.appfunctions.sidecar;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.appsearch.GenericDocument;
-import android.os.Bundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * The response to an app function execution.
- *
- * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
- * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
- * platform API).
- */
-public final class ExecuteAppFunctionResponse {
- /**
- * The name of the property that stores the function return value within the {@code
- * resultDocument}.
- *
- * <p>See {@link GenericDocument#getProperty(String)} for more information.
- *
- * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
- * be empty {@link GenericDocument}.
- *
- * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
- * return {@code null}.
- *
- * <p>See {@link #getResultDocument} for more information on extracting the return value.
- */
- public static final String PROPERTY_RETURN_VALUE = "returnValue";
-
- /**
- * The call was successful.
- *
- * <p>This result code does not belong in an error category.
- */
- public static final int RESULT_OK = 0;
-
- /**
- * The caller does not have the permission to execute an app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DENIED = 1000;
-
- /**
- * The caller supplied invalid arguments to the execution request.
- *
- * <p>This error may be considered similar to {@link IllegalArgumentException}.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_INVALID_ARGUMENT = 1001;
-
- /**
- * The caller tried to execute a disabled app function.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_DISABLED = 1002;
-
- /**
- * The caller tried to execute a function that does not exist.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
- */
- public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
- /**
- * An internal unexpected error coming from the system.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_SYSTEM_ERROR = 2000;
-
- /**
- * The operation was cancelled. Use this error code to report that a cancellation is done after
- * receiving a cancellation signal.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
- */
- public static final int RESULT_CANCELLED = 2001;
-
- /**
- * An unknown error occurred while processing the call in the AppFunctionService.
- *
- * <p>This error is thrown when the service is connected in the remote application but an
- * unexpected error is thrown from the bound application.
- *
- * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
- */
- public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
- /**
- * The error category is unknown.
- *
- * <p>This is the default value for {@link #getErrorCategory}.
- */
- public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
- /**
- * The error is caused by the app requesting a function execution.
- *
- * <p>For example, the caller provided invalid parameters in the execution request e.g. an
- * invalid function ID.
- *
- * <p>Errors in the category fall in the range 1000-1999 inclusive.
- */
- public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
- /**
- * The error is caused by an issue in the system.
- *
- * <p>For example, the AppFunctionService implementation is not found by the system.
- *
- * <p>Errors in the category fall in the range 2000-2999 inclusive.
- */
- public static final int ERROR_CATEGORY_SYSTEM = 2;
-
- /**
- * The error is caused by the app providing the function.
- *
- * <p>For example, the app crashed when the system is executing the request.
- *
- * <p>Errors in the category fall in the range 3000-3999 inclusive.
- */
- public static final int ERROR_CATEGORY_APP = 3;
-
- /** The result code of the app function execution. */
- @ResultCode private final int mResultCode;
-
- /**
- * The error message associated with the result, if any. This is {@code null} if the result code
- * is {@link #RESULT_OK}.
- */
- @Nullable private final String mErrorMessage;
-
- /**
- * Returns the return value of the executed function.
- *
- * <p>The return value is stored in a {@link GenericDocument} with the key {@link
- * #PROPERTY_RETURN_VALUE}.
- *
- * <p>See {@link #getResultDocument} for more information on extracting the return value.
- */
- @NonNull private final GenericDocument mResultDocument;
-
- /** Returns the additional metadata data relevant to this function execution response. */
- @NonNull private final Bundle mExtras;
-
- private ExecuteAppFunctionResponse(
- @NonNull GenericDocument resultDocument,
- @NonNull Bundle extras,
- @ResultCode int resultCode,
- @Nullable String errorMessage) {
- mResultDocument = Objects.requireNonNull(resultDocument);
- mExtras = Objects.requireNonNull(extras);
- mResultCode = resultCode;
- mErrorMessage = errorMessage;
- }
-
- /**
- * Returns result codes from throwable.
- *
- * @hide
- */
- static @ResultCode int getResultCode(@NonNull Throwable t) {
- if (t instanceof IllegalArgumentException) {
- return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
- }
- return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
- }
-
- /**
- * Returns a successful response.
- *
- * @param resultDocument The return value of the executed function.
- * @param extras The additional metadata data relevant to this function execution response.
- */
- @NonNull
- public static ExecuteAppFunctionResponse newSuccess(
- @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
- Objects.requireNonNull(resultDocument);
- Bundle actualExtras = getActualExtras(extras);
-
- return new ExecuteAppFunctionResponse(
- resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
- }
-
- /**
- * Returns a failure response.
- *
- * @param resultCode The result code of the app function execution.
- * @param extras The additional metadata data relevant to this function execution response.
- * @param errorMessage The error message associated with the result, if any.
- */
- @NonNull
- public static ExecuteAppFunctionResponse newFailure(
- @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
- if (resultCode == RESULT_OK) {
- throw new IllegalArgumentException("resultCode must not be RESULT_OK");
- }
- Bundle actualExtras = getActualExtras(extras);
- GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
- return new ExecuteAppFunctionResponse(
- emptyDocument, actualExtras, resultCode, errorMessage);
- }
-
- private static Bundle getActualExtras(@Nullable Bundle extras) {
- if (extras == null) {
- return Bundle.EMPTY;
- }
- return extras;
- }
-
- /**
- * Returns the error category of the {@link ExecuteAppFunctionResponse}.
- *
- * <p>This method categorizes errors based on their underlying cause, allowing developers to
- * implement targeted error handling and provide more informative error messages to users. It
- * maps ranges of result codes to specific error categories.
- *
- * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
- * ensure correct categorization of the failed response.
- *
- * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
- * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
- *
- * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
- * result code ranges.
- */
- @ErrorCategory
- public int getErrorCategory() {
- if (mResultCode >= 1000 && mResultCode < 2000) {
- return ERROR_CATEGORY_REQUEST_ERROR;
- }
- if (mResultCode >= 2000 && mResultCode < 3000) {
- return ERROR_CATEGORY_SYSTEM;
- }
- if (mResultCode >= 3000 && mResultCode < 4000) {
- return ERROR_CATEGORY_APP;
- }
- return ERROR_CATEGORY_UNKNOWN;
- }
-
- /**
- * Returns a generic document containing the return value of the executed function.
- *
- * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
- *
- * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
- * function does not produce a return value.
- *
- * <p>Sample code for extracting the return value:
- *
- * <pre>
- * GenericDocument resultDocument = response.getResultDocument();
- * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
- * if (returnValue != null) {
- * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
- * // {@link GenericDocument#getPropertyLong} etc.
- * // Do something with the returnValue
- * }
- * </pre>
- */
- @NonNull
- public GenericDocument getResultDocument() {
- return mResultDocument;
- }
-
- /** Returns the extras of the app function execution. */
- @NonNull
- public Bundle getExtras() {
- return mExtras;
- }
-
- /**
- * Returns {@code true} if {@link #getResultCode} equals {@link
- * ExecuteAppFunctionResponse#RESULT_OK}.
- */
- public boolean isSuccess() {
- return getResultCode() == RESULT_OK;
- }
-
- /**
- * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
- */
- @ResultCode
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Returns the error message associated with this result.
- *
- * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
- */
- @Nullable
- public String getErrorMessage() {
- return mErrorMessage;
- }
-
- /**
- * Result codes.
- *
- * @hide
- */
- @IntDef(
- prefix = {"RESULT_"},
- value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_SYSTEM_ERROR,
- RESULT_FUNCTION_NOT_FOUND,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED,
- RESULT_CANCELLED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
-
- /**
- * Error categories.
- *
- * @hide
- */
- @IntDef(
- prefix = {"ERROR_CATEGORY_"},
- value = {
- ERROR_CATEGORY_UNKNOWN,
- ERROR_CATEGORY_REQUEST_ERROR,
- ERROR_CATEGORY_APP,
- ERROR_CATEGORY_SYSTEM
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCategory {}
-}
diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp
index 6f5eff305d8d..db79675ae9f7 100644
--- a/libs/appfunctions/tests/Android.bp
+++ b/libs/appfunctions/tests/Android.bp
@@ -25,7 +25,7 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.core_core-ktx",
- "com.google.android.appfunctions.sidecar.impl",
+ "com.android.extensions.appfunctions.impl",
"junit",
"kotlin-test",
"mockito-target-extended-minus-junit4",
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
index 264f84209caf..11202d58e484 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-package com.google.android.appfunctions.sidecar.tests
+package com.android.extensions.appfunctions.tests
+import android.app.appfunctions.AppFunctionException
import android.app.appfunctions.ExecuteAppFunctionRequest
import android.app.appfunctions.ExecuteAppFunctionResponse
import android.app.appsearch.GenericDocument
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.appfunctions.sidecar.SidecarConverter
+import com.android.extensions.appfunctions.SidecarConverter
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,7 +61,7 @@ class SidecarConverterTest {
.setPropertyLong("testLong", 23)
.build()
val sidecarRequest =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder(
+ com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder(
"targetPkg",
"targetFunctionId"
)
@@ -83,44 +84,38 @@ class SidecarConverterTest {
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null)
+ val platformResponse = ExecuteAppFunctionResponse(resultGd)
val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
platformResponse
)
- assertThat(sidecarResponse.isSuccess).isTrue()
assertThat(
sidecarResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(sidecarResponse.errorMessage).isNull()
}
@Test
- fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val platformResponse =
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getSidecarAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val platformException =
+ AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse
+ val sidecarException = SidecarConverter.getSidecarAppFunctionException(
+ platformException
)
- assertThat(sidecarResponse.isSuccess).isFalse()
- assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(sidecarResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(sidecarResponse.errorMessage).isNull()
+ assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(sidecarException.errorMessage).isEqualTo("error")
+ assertThat(sidecarException.extras.getString("key")).isEqualTo("value")
}
@Test
@@ -129,44 +124,39 @@ class SidecarConverterTest {
GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
.setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
.build()
- val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse
- .newSuccess(resultGd, null)
+ val sidecarResponse =
+ com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd)
val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
sidecarResponse
)
- assertThat(platformResponse.isSuccess).isTrue()
assertThat(
platformResponse.resultDocument.getProperty(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
)
)
.isEqualTo(booleanArrayOf(true))
- assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
- assertThat(platformResponse.errorMessage).isNull()
}
@Test
- fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
- val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
- val sidecarResponse =
- com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- null,
- null
+ fun getPlatformAppFunctionException_sameContents() {
+ val bundle = Bundle()
+ bundle.putString("key", "value")
+ val sidecarException =
+ com.android.extensions.appfunctions.AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "error",
+ bundle
)
- val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
- sidecarResponse
+ val platformException = SidecarConverter.getPlatformAppFunctionException(
+ sidecarException
)
- assertThat(platformResponse.isSuccess).isFalse()
- assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
- assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
- assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
- assertThat(platformResponse.resultCode)
- .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
- assertThat(platformResponse.errorMessage).isNull()
+ assertThat(platformException.errorCode)
+ .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+ assertThat(platformException.errorMessage).isEqualTo("error")
+ assertThat(platformException.extras.getString("key")).isEqualTo("value")
}
}
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 88efed55c11f..3f9126aa9456 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -1000,7 +1000,10 @@ public final class MediaCas implements AutoCloseable {
@SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public boolean updateResourcePriority(int priority, int niceValue) {
- return mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
+ if (mTunerResourceManager != null) {
+ return mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
+ }
+ return false;
}
/**
@@ -1017,7 +1020,9 @@ public final class MediaCas implements AutoCloseable {
@SystemApi
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public void setResourceHolderRetain(boolean resourceHolderRetain) {
- mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ if (mTunerResourceManager != null) {
+ mTunerResourceManager.setResourceHolderRetain(mClientId, resourceHolderRetain);
+ }
}
IHwBinder getBinder() {
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index b08a86ee8f46..bd65b2ecb76a 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -17,6 +17,7 @@
package android.media;
import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
+import static android.media.codec.Flags.FLAG_NUM_INPUT_SLOTS;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
import static android.media.codec.Flags.FLAG_APV_SUPPORT;
@@ -1777,6 +1778,17 @@ public final class MediaFormat {
public static final String KEY_SECURITY_MODEL = "security-model";
/**
+ * A key describing the number of slots used in the codec. When present in input format,
+ * the associated value indicates the number of input slots. The entry is set by the codec
+ * if configured with (@link MediaCodec#CONFIGURE_FLAG_BLOCK_MODEL), and will be ignored if set
+ * by the application.
+ * <p>
+ * The associated value is an integer.
+ */
+ @FlaggedApi(FLAG_NUM_INPUT_SLOTS)
+ public static final String KEY_NUM_SLOTS = "num-slots";
+
+ /**
* QpOffsetRect constitutes the metadata required for encoding a region of interest in an
* image or a video frame. The region of interest is represented by a rectangle. The four
* integer coordinates of the rectangle are stored in fields left, top, right, bottom.
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index c1d73f9033cf..8521d1c472a8 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -705,6 +705,10 @@ public class AidlConversion {
aidl.type = AudioDeviceType.OUT_BROADCAST;
aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
break;
+ case AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP:
+ aidl.type = AudioDeviceType.OUT_MULTICHANNEL_GROUP;
+ aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
+ break;
case AudioSystem.DEVICE_IN_BUILTIN_MIC:
aidl.type = AudioDeviceType.IN_MICROPHONE;
break;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 90b4aba690d0..ba2398c12607 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -51,7 +51,7 @@ flag {
is_exported: true
namespace: "media_tv"
description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE"
- bug: "301713440"
+ bug: "375691732"
}
flag {
@@ -79,7 +79,7 @@ flag {
flag {
name: "update_client_profile_priority"
- namespace: "media"
+ namespace: "media_solutions"
description : "Feature flag to add updateResourcePriority api to MediaCas"
bug: "300565729"
}
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 391eb225bcab..bb782bf1aee4 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -26,6 +26,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
+ * Settings for ambient backlight.
* @hide
*/
public class AmbientBacklightSettings implements Parcelable {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index e6c79dd9681f..250d59b7c2d7 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,20 +30,22 @@ import android.media.quality.SoundProfile;
*/
interface IMediaQualityManager {
PictureProfile createPictureProfile(in PictureProfile pp);
- void updatePictureProfile(in long id, in PictureProfile pp);
- void removePictureProfile(in long id);
- PictureProfile getPictureProfileById(in long id);
+ void updatePictureProfile(in String id, in PictureProfile pp);
+ void removePictureProfile(in String id);
+ PictureProfile getPictureProfile(in int type, in String name);
List<PictureProfile> getPictureProfilesByPackage(in String packageName);
List<PictureProfile> getAvailablePictureProfiles();
- List<PictureProfile> getAllPictureProfiles();
+ List<String> getPictureProfilePackageNames();
+ List<String> getPictureProfileAllowList();
+ void setPictureProfileAllowList(in List<String> packages);
SoundProfile createSoundProfile(in SoundProfile pp);
- void updateSoundProfile(in long id, in SoundProfile pp);
- void removeSoundProfile(in long id);
- SoundProfile getSoundProfileById(in long id);
+ void updateSoundProfile(in String id, in SoundProfile pp);
+ void removeSoundProfile(in String id);
+ SoundProfile getSoundProfileById(in String id);
List<SoundProfile> getSoundProfilesByPackage(in String packageName);
List<SoundProfile> getAvailableSoundProfiles();
- List<SoundProfile> getAllSoundProfiles();
+ List<String> getSoundProfilePackageNames();
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 05441cde31e7..34aa2b061caf 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -17,6 +17,7 @@
package android.media.quality;
+import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
/**
@@ -24,7 +25,9 @@ import android.media.quality.PictureProfile;
* @hide
*/
oneway interface IPictureProfileCallback {
- void onPictureProfileAdded(in long id, in PictureProfile p);
- void onPictureProfileUpdated(in long id, in PictureProfile p);
- void onPictureProfileRemoved(in long id, in PictureProfile p);
+ void onPictureProfileAdded(in String id, in PictureProfile p);
+ void onPictureProfileUpdated(in String id, in PictureProfile p);
+ void onPictureProfileRemoved(in String id, in PictureProfile p);
+ void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+ void onError(in int err);
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 38a2025535f4..26d83aca3e7b 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -19,6 +19,7 @@ package android.media.quality;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.media.tv.flags.Flags;
@@ -63,7 +64,7 @@ public final class MediaQualityManager {
mService = service;
IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
@Override
- public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+ public void onPictureProfileAdded(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -72,7 +73,7 @@ public final class MediaQualityManager {
}
}
@Override
- public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+ public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -81,7 +82,7 @@ public final class MediaQualityManager {
}
}
@Override
- public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+ public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -89,6 +90,24 @@ public final class MediaQualityManager {
}
}
}
+ @Override
+ public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+ synchronized (mLock) {
+ for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+ // TODO: filter callback record
+ record.postParamCapabilitiesChanged(profileId, caps);
+ }
+ }
+ }
+ @Override
+ public void onError(int err) {
+ synchronized (mLock) {
+ for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+ // TODO: filter callback record
+ record.postError(err);
+ }
+ }
+ }
};
ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
@Override
@@ -175,14 +194,17 @@ public final class MediaQualityManager {
/**
- * Gets picture profile by given profile ID.
- * @return the corresponding picture profile if available; {@code null} if the ID doesn't
- * exist or the profile is not accessible to the caller.
+ * Gets picture profile by given profile type and name.
+ *
+ * @return the corresponding picture profile if available; {@code null} if the name doesn't
+ * exist.
* @hide
*/
- public PictureProfile getPictureProfileById(long profileId) {
+ @Nullable
+ public PictureProfile getPictureProfile(
+ @PictureProfile.ProfileType int type, @NonNull String name) {
try {
- return mService.getPictureProfileById(profileId);
+ return mService.getPictureProfile(type, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -190,11 +212,13 @@ public final class MediaQualityManager {
/**
- * @SystemApi gets profiles that available to the given package
- * @hide
+ * Gets profiles that available to the given package.
+ *
+ * @hide @SystemApi
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+ public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
try {
return mService.getPictureProfilesByPackage(packageName);
} catch (RemoteException e) {
@@ -215,13 +239,16 @@ public final class MediaQualityManager {
}
/**
- * @SystemApi all stored picture profiles of all packages
- * @hide
+ * Gets all package names whose picture profiles are available.
+ *
+ * @see #getPictureProfilesByPackage(String)
+ * @hide @SystemApi
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getAllPictureProfiles() {
+ public List<String> getPictureProfilePackageNames() {
try {
- return mService.getAllPictureProfiles();
+ return mService.getPictureProfilePackageNames();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -231,10 +258,12 @@ public final class MediaQualityManager {
/**
* Creates a picture profile and store it in the system.
*
- * @return the stored profile with an assigned profile ID.
+ * @return the stored profile with an assigned profile ID. {@code null} if it's not created
+ * successfully.
* @hide
*/
- public PictureProfile createPictureProfile(PictureProfile pp) {
+ @Nullable
+ public PictureProfile createPictureProfile(@NonNull PictureProfile pp) {
try {
return mService.createPictureProfile(pp);
} catch (RemoteException e) {
@@ -247,7 +276,7 @@ public final class MediaQualityManager {
* Updates an existing picture profile and store it in the system.
* @hide
*/
- public void updatePictureProfile(long profileId, PictureProfile pp) {
+ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
try {
mService.updatePictureProfile(profileId, pp);
} catch (RemoteException e) {
@@ -260,7 +289,7 @@ public final class MediaQualityManager {
* Removes a picture profile from the system.
* @hide
*/
- public void removePictureProfile(long profileId) {
+ public void removePictureProfile(@NonNull String profileId) {
try {
mService.removePictureProfile(profileId);
} catch (RemoteException e) {
@@ -307,7 +336,7 @@ public final class MediaQualityManager {
* exist or the profile is not accessible to the caller.
* @hide
*/
- public SoundProfile getSoundProfileById(long profileId) {
+ public SoundProfile getSoundProfileById(String profileId) {
try {
return mService.getSoundProfileById(profileId);
} catch (RemoteException e) {
@@ -346,9 +375,9 @@ public final class MediaQualityManager {
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
- public List<SoundProfile> getAllSoundProfiles() {
+ public List<String> getSoundProfilePackageNames() {
try {
- return mService.getAllSoundProfiles();
+ return mService.getSoundProfilePackageNames();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -374,7 +403,7 @@ public final class MediaQualityManager {
* Updates an existing sound profile and store it in the system.
* @hide
*/
- public void updateSoundProfile(long profileId, SoundProfile sp) {
+ public void updateSoundProfile(String profileId, SoundProfile sp) {
try {
mService.updateSoundProfile(profileId, sp);
} catch (RemoteException e) {
@@ -387,7 +416,7 @@ public final class MediaQualityManager {
* Removes a sound profile from the system.
* @hide
*/
- public void removeSoundProfile(long profileId) {
+ public void removeSoundProfile(String profileId) {
try {
mService.removeSoundProfile(profileId);
} catch (RemoteException e) {
@@ -399,7 +428,8 @@ public final class MediaQualityManager {
* Gets capability information of the given parameters.
* @hide
*/
- public List<ParamCapability> getParamCapabilities(List<String> names) {
+ @NonNull
+ public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
try {
return mService.getParamCapabilities(names);
} catch (RemoteException e) {
@@ -408,7 +438,38 @@ public final class MediaQualityManager {
}
/**
+ * Gets the allowlist of packages that can create and removed picture profiles
+ *
+ * @see #createPictureProfile(PictureProfile)
+ * @see #removePictureProfile(String)
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ @NonNull
+ public List<String> getPictureProfileAllowList() {
+ try {
+ return mService.getPictureProfileAllowList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the allowlist of packages that can create and removed picture profiles
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
+ try {
+ mService.setPictureProfileAllowList(packageNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+ * @hide
*/
public boolean isSupported() {
try {
@@ -581,7 +642,7 @@ public final class MediaQualityManager {
return mCallback;
}
- public void postPictureProfileAdded(final long id, PictureProfile profile) {
+ public void postPictureProfileAdded(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
@@ -591,7 +652,7 @@ public final class MediaQualityManager {
});
}
- public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+ public void postPictureProfileUpdated(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -600,7 +661,7 @@ public final class MediaQualityManager {
});
}
- public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+ public void postPictureProfileRemoved(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -608,6 +669,24 @@ public final class MediaQualityManager {
}
});
}
+
+ public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onParamCapabilitiesChanged(id, caps);
+ }
+ });
+ }
+
+ public void postError(int error) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError(error);
+ }
+ });
+ }
}
private static final class SoundProfileCallbackRecord {
@@ -681,24 +760,57 @@ public final class MediaQualityManager {
*/
public abstract static class PictureProfileCallback {
/**
+ * This is invoked when a picture profile has been added.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the newly added profile.
* @hide
*/
- public void onPictureProfileAdded(long id, PictureProfile profile) {
+ public void onPictureProfileAdded(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when a picture profile has been updated.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the profile with updated info.
* @hide
*/
- public void onPictureProfileUpdated(long id, PictureProfile profile) {
+ public void onPictureProfileUpdated(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when a picture profile has been removed.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the removed profile.
* @hide
*/
- public void onPictureProfileRemoved(long id, PictureProfile profile) {
+ public void onPictureProfileRemoved(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when an issue has occurred.
+ *
+ * @param errorCode the error code
* @hide
*/
- public void onError(int errorCode) {
+ public void onError(@PictureProfile.ErrorCode int errorCode) {
+ }
+
+ /**
+ * This is invoked when parameter capabilities has been changed due to status changes of the
+ * content.
+ *
+ * @param profileId the ID of the profile used by the media content.
+ * @param updatedCaps the updated capabilities.
+ * @hide
+ */
+ public void onParamCapabilitiesChanged(
+ @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
index 70e85920c12f..0b698a9c1ad2 100644
--- a/media/java/android/media/quality/ParamCapability.java
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -34,7 +34,7 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
-public class ParamCapability implements Parcelable {
+public final class ParamCapability implements Parcelable {
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -104,6 +104,7 @@ public class ParamCapability implements Parcelable {
@NonNull
private final Bundle mCaps;
+ /** @hide */
protected ParamCapability(Parcel in) {
mName = in.readString();
mIsSupported = in.readBoolean();
@@ -112,7 +113,7 @@ public class ParamCapability implements Parcelable {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mName);
dest.writeBoolean(mIsSupported);
dest.writeInt(mType);
@@ -124,6 +125,7 @@ public class ParamCapability implements Parcelable {
return 0;
}
+ @NonNull
public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
@Override
public ParamCapability createFromParcel(Parcel in) {
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 8fb57124d33e..2be47dd87ef2 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -71,6 +71,53 @@ public final class PictureProfile implements Parcelable {
*/
public static final int TYPE_APPLICATION = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "ERROR_", value = {
+ ERROR_UNKNOWN,
+ ERROR_NO_PERMISSION,
+ ERROR_DUPLICATE,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_NOT_ALLOWLISTED
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * Error code for unknown errors.
+ * @hide
+ */
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Error code for missing necessary permission to handle the profiles.
+ * @hide
+ */
+ public static final int ERROR_NO_PERMISSION = 1;
+
+ /**
+ * Error code for creating a profile with existing profile type and name.
+ *
+ * @see #getProfileType()
+ * @see #getName()
+ * @hide
+ */
+ public static final int ERROR_DUPLICATE = 2;
+
+ /**
+ * Error code for invalid argument.
+ * @hide
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 3;
+
+ /**
+ * Error code for the case when an operation requires an allowlist but the caller is not in the
+ * list.
+ *
+ * @see MediaQualityManager#getPictureProfileAllowList()
+ * @hide
+ */
+ public static final int ERROR_NOT_ALLOWLISTED = 4;
+
private PictureProfile(@NonNull Parcel in) {
mId = in.readString();
diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
index 09573909c288..d9a1221e529c 100644
--- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
+++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
@@ -18,6 +18,7 @@ package android.media.audio.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -504,6 +505,27 @@ public final class AidlConversionUnitTests {
assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type);
}
+ @Test
+ public void testAudioDeviceDescriptionConversion() {
+ for (int nativeDeviceType : AudioSystem.DEVICE_OUT_ALL_SET) {
+ assertNotEquals(
+ AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType)
+ .type,
+ AudioDeviceType.NONE);
+ }
+
+ for (int nativeDeviceType : AudioSystem.DEVICE_IN_ALL_SET) {
+ if (nativeDeviceType == AudioSystem.DEVICE_IN_COMMUNICATION
+ || nativeDeviceType == AudioSystem.DEVICE_IN_AMBIENT) {
+ continue;
+ }
+ assertNotEquals(
+ AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType)
+ .type,
+ AudioDeviceType.NONE);
+ }
+ }
+
private static AudioFormatDescription createPcm16FormatAidl() {
final AudioFormatDescription aidl = new AudioFormatDescription();
aidl.type = AudioFormatType.PCM;
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3eb99c3387f7..da29c49f9d7b 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -55,6 +55,7 @@ cc_library_shared {
"surface_control_input_receiver.cpp",
"choreographer.cpp",
"configuration.cpp",
+ "dynamic_instrumentation_manager.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
"input_transfer_token.cpp",
@@ -100,6 +101,7 @@ cc_library_shared {
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
"android.os.flags-aconfig-cc",
+ "dynamic_instrumentation_manager_aidl-cpp",
"libnativedisplay",
"libfmq",
],
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
new file mode 100644
index 000000000000..d9bacb116f96
--- /dev/null
+++ b/native/android/dynamic_instrumentation_manager.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ADynamicInstrumentationManager"
+#include <android/dynamic_instrumentation_manager.h>
+#include <android/os/instrumentation/ExecutableMethodFileOffsets.h>
+#include <android/os/instrumentation/IDynamicInstrumentationManager.h>
+#include <android/os/instrumentation/MethodDescriptor.h>
+#include <android/os/instrumentation/TargetProcess.h>
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace android::dynamicinstrumentationmanager {
+
+// Global instance of IDynamicInstrumentationManager, service is obtained only on first use.
+static std::mutex mLock;
+static sp<os::instrumentation::IDynamicInstrumentationManager> mService;
+
+sp<os::instrumentation::IDynamicInstrumentationManager> getService() {
+ std::lock_guard<std::mutex> scoped_lock(mLock);
+ if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) {
+ sp<IBinder> binder =
+ defaultServiceManager()->waitForService(String16("dynamic_instrumentation"));
+ mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder);
+ }
+ return mService;
+}
+
+} // namespace android::dynamicinstrumentationmanager
+
+using namespace android;
+using namespace dynamicinstrumentationmanager;
+
+struct ADynamicInstrumentationManager_TargetProcess {
+ uid_t uid;
+ uid_t pid;
+ std::string processName;
+
+ ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName)
+ : uid(uid), pid(pid), processName(processName) {}
+};
+
+ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create(
+ uid_t uid, pid_t pid, const char* processName) {
+ return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName);
+}
+
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+ ADynamicInstrumentationManager_TargetProcess* instance) {
+ delete instance;
+}
+
+struct ADynamicInstrumentationManager_MethodDescriptor {
+ std::string fqcn;
+ std::string methodName;
+ std::vector<std::string> fqParameters;
+
+ ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName,
+ const char* fullyQualifiedParameters[],
+ size_t numParameters)
+ : fqcn(fqcn), methodName(methodName) {
+ std::vector<std::string> fqParameters;
+ fqParameters.reserve(numParameters);
+ std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters));
+ this->fqParameters = std::move(fqParameters);
+ }
+};
+
+ADynamicInstrumentationManager_MethodDescriptor*
+ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName,
+ const char* methodName,
+ const char* fullyQualifiedParameters[],
+ size_t numParameters) {
+ return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName,
+ fullyQualifiedParameters,
+ numParameters);
+}
+
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+ ADynamicInstrumentationManager_MethodDescriptor* instance) {
+ delete instance;
+}
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets {
+ std::string containerPath;
+ uint64_t containerOffset;
+ uint64_t methodOffset;
+};
+
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets*
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() {
+ return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+}
+
+const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->containerPath.c_str();
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->containerOffset;
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->methodOffset;
+}
+
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ delete instance;
+}
+
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+ const ADynamicInstrumentationManager_TargetProcess* targetProcess,
+ const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor,
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) {
+ android::os::instrumentation::TargetProcess targetProcessParcel;
+ targetProcessParcel.uid = targetProcess->uid;
+ targetProcessParcel.pid = targetProcess->pid;
+ targetProcessParcel.processName = targetProcess->processName;
+
+ android::os::instrumentation::MethodDescriptor methodDescriptorParcel;
+ methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn;
+ methodDescriptorParcel.methodName = methodDescriptor->methodName;
+ methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters;
+
+ sp<os::instrumentation::IDynamicInstrumentationManager> service = getService();
+ if (service == nullptr) {
+ return INVALID_OPERATION;
+ }
+
+ std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets;
+ binder_status_t result =
+ service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel,
+ &offsets)
+ .exceptionCode();
+ if (result != OK) {
+ return result;
+ }
+
+ if (offsets != std::nullopt) {
+ auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+ value->containerPath = offsets->containerPath;
+ value->containerOffset = offsets->containerOffset;
+ value->methodOffset = offsets->methodOffset;
+ *out = value;
+ } else {
+ *out = nullptr;
+ }
+
+ return result;
+} \ No newline at end of file
diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h
new file mode 100644
index 000000000000..6c46288954bf
--- /dev/null
+++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__
+#define __ADYNAMICINSTRUMENTATIONMANAGER_H__
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct ADynamicInstrumentationManager_MethodDescriptor;
+typedef struct ADynamicInstrumentationManager_MethodDescriptor
+ ADynamicInstrumentationManager_MethodDescriptor;
+
+struct ADynamicInstrumentationManager_TargetProcess;
+typedef struct ADynamicInstrumentationManager_TargetProcess
+ ADynamicInstrumentationManager_TargetProcess;
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+
+/**
+ * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are
+ * done with ADynamicInstrumentationManager_TargetProcess_destroy.
+ *
+ * @param uid of targeted process.
+ * @param pid of targeted process.
+ * @param processName to disambiguate from corner cases that may arise from pid reuse.
+ */
+ADynamicInstrumentationManager_TargetProcess* _Nonnull
+ ADynamicInstrumentationManager_TargetProcess_create(
+ uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_TargetProcess.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create.
+ */
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+ ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they
+ * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy.
+ *
+ * @param fullyQualifiedClassName fqcn of class containing the method.
+ * @param methodName
+ * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for
+ * primitives.
+ * @param numParameters length of `fullyQualifiedParameters` array.
+ */
+ADynamicInstrumentationManager_MethodDescriptor* _Nonnull
+ ADynamicInstrumentationManager_MethodDescriptor_create(
+ const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName,
+ const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters)
+ __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_MethodDescriptor.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create.
+ */
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+ ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Get the containerPath calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The OS path of the containing file.
+ */
+const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Get the containerOffset calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the containing file within the process' memory.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the method within the containing file.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ */
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Provides ART metadata about the described java method within the target process.
+ *
+ * @param targetProcess describes for which process the data is requested.
+ * @param methodDescriptor describes the targeted method.
+ * @param out will be populated with the data if successful. A nullptr combined
+ * with an OK status means that the program method is defined, but the offset
+ * info was unavailable because it is not AOT compiled.
+ * @return status indicating success or failure. The values correspond to the `binder_exception_t`
+ * enum values from <android/binder_status.h>.
+ */
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+ const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess,
+ const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor,
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull* _Nullable out)
+ __INTRODUCED_IN(36);
+
+__END_DECLS
+
+#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b025cb880ee7..a0460572abfc 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -4,6 +4,15 @@ LIBANDROID {
AActivityManager_removeUidImportanceListener; # systemapi introduced=31
AActivityManager_isUidActive; # systemapi introduced=31
AActivityManager_getUidImportance; # systemapi introduced=31
+ ADynamicInstrumentationManager_TargetProcess_create; # systemapi
+ ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi
+ ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi
+ ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi
+ ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi
AAssetDir_close;
AAssetDir_getNextFileName;
AAssetDir_rewind;
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index e1418678a9f8..b2dcb7fa4f53 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,7 +61,7 @@ android_library {
"SettingsLibUtils",
"SettingsLibZeroStatePreference",
"settingslib_media_flags_lib",
- "settingslib_flags_lib",
+ "aconfig_settingslib_flags_java_lib",
],
plugins: ["androidx.room_room-compiler-plugin"],
@@ -107,20 +107,6 @@ java_aconfig_library {
aconfig_declarations: "settingslib_media_flags",
}
-aconfig_declarations {
- name: "settingslib_flags",
- package: "com.android.settingslib.flags",
- container: "system",
- srcs: [
- "aconfig/settingslib.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "settingslib_flags_lib",
- aconfig_declarations: "settingslib_flags",
-}
-
soong_config_module_type {
name: "avatar_picker_java_defaults",
module_type: "java_defaults",
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
index 3b52df7e5fbb..c3f6eb71c2e7 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
@@ -30,21 +30,28 @@ public class AppPreference extends Preference {
public AppPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppPreference(Context context) {
super(context);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- setLayoutResource(R.layout.preference_app);
+ init(context);
+ }
+
+ private void init(Context context) {
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? com.android.settingslib.widget.theme.R.layout.settingslib_expressive_preference
+ : R.layout.preference_app;
+ setLayoutResource(resId);
}
}
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
index ecd500e1a160..3dcdfbaeb8b3 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
@@ -32,22 +32,29 @@ public class AppSwitchPreference extends SwitchPreferenceCompat {
public AppSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- setLayoutResource(R.layout.preference_app);
+ init(context);
}
public AppSwitchPreference(Context context) {
super(context);
- setLayoutResource(R.layout.preference_app);
+ init(context);
+ }
+
+ private void init(Context context) {
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? com.android.settingslib.widget.theme.R.layout.settingslib_expressive_preference
+ : R.layout.preference_app;
+ setLayoutResource(resId);
}
@Override
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 979ff96be3f7..993555e78bea 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -37,7 +37,7 @@ import com.google.android.material.button.MaterialButton;
/**
* A preference handled a button
*/
-public class ButtonPreference extends Preference {
+public class ButtonPreference extends Preference implements GroupSectionDividerMixin {
enum ButtonStyle {
FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index d60290ed91ef..37f47543c536 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -43,7 +43,7 @@ import java.net.URISyntaxException;
* A custom preference acting as "footer" of a page. It has a field for icon and text. It is added
* to screen as the last preference.
*/
-public class FooterPreference extends Preference {
+public class FooterPreference extends Preference implements GroupSectionDividerMixin {
private static final String TAG = "FooterPreference";
public static final String KEY_FOOTER = "footer_preference";
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
index 155db186c702..8f9fb7a2e408 100644
--- a/packages/SettingsLib/IntroPreference/Android.bp
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -29,5 +29,6 @@ android_library {
min_sdk_version: "21",
apex_available: [
"//apex_available:platform",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 155ee831ae52..78e27fe5ad04 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -29,6 +29,7 @@ android_library {
"//apex_available:platform",
"com.android.permission",
"com.android.mediaprovider",
+ "com.android.healthfitness",
],
}
@@ -51,5 +52,6 @@ java_aconfig_library {
"//apex_available:platform",
"com.android.permission",
"com.android.mediaprovider",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index ccdf37d452b0..0cd0b3cb14f1 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,7 @@
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
android:gravity="center"
- android:layout_marginEnd="-8dp"
+ android:layout_marginEnd="-4dp"
android:filterTouchesWhenObscured="false">
<androidx.preference.internal.PreferenceImageView
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml
index 3c69027c2080..cec8e45e2bfb 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml
@@ -36,33 +36,34 @@
<style name="SettingsLibPreference.SwitchPreference" parent="SettingsSwitchPreference.SettingsLib"/>
<style name="SettingsLibPreference.Expressive">
- <item name="android:layout">@layout/settingslib_expressive_preference</item>
+ <item name="layout">@layout/settingslib_expressive_preference</item>
</style>
<style name="SettingsLibPreference.Category.Expressive">
</style>
<style name="SettingsLibPreference.CheckBoxPreference.Expressive">
- <item name="android:layout">@layout/settingslib_expressive_preference</item>
+ <item name="layout">@layout/settingslib_expressive_preference</item>
</style>
<style name="SettingsLibPreference.SwitchPreferenceCompat.Expressive">
- <item name="android:layout">@layout/settingslib_expressive_preference</item>
+ <item name="layout">@layout/settingslib_expressive_preference</item>
<item name="android:widgetLayout">@layout/settingslib_expressive_preference_switch</item>
</style>
<style name="SettingsLibPreference.SeekBarPreference.Expressive"/>
<style name="SettingsLibPreference.PreferenceScreen.Expressive">
- <item name="android:layout">@layout/settingslib_expressive_preference</item>
+ <item name="layout">@layout/settingslib_expressive_preference</item>
</style>
<style name="SettingsLibPreference.DialogPreference.Expressive">
+ <item name="layout">@layout/settingslib_expressive_preference</item>
</style>
<style name="SettingsLibPreference.DialogPreference.EditTextPreference.Expressive">
- <item name="android:layout">@layout/settingslib_expressive_preference</item>
- <item name="android:dialogLayout">@layout/settingslib_preference_dialog_edittext</item>
+ <item name="layout">@layout/settingslib_expressive_preference</item>
+ <item name="dialogLayout">@layout/settingslib_preference_dialog_edittext</item>
</style>
<style name="SettingsLibPreference.DropDown.Expressive">
diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
index c1578ef69635..1f8cfb5e432e 100644
--- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
+++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
@@ -34,7 +34,7 @@ class StatusBannerPreference @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
-) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
enum class BannerStatus {
GENERIC,
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 5be56f8ebc86..9764e64b8509 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -31,7 +31,7 @@ open class TopIntroPreference @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
-) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
private var isCollapsable: Boolean = false
private var minLines: Int = 2
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 0dc772ab6ecd..ebd5a1deffd2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -224,7 +224,7 @@ public class BluetoothEventManager {
// audio sharing is enabled.
if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
&& state == BluetoothAdapter.STATE_DISCONNECTED
- && BluetoothUtils.isAudioSharingEnabled()) {
+ && BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
if (profileManager != null
&& profileManager.getLeAudioBroadcastProfile() != null
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 612c193da9c3..a87b8153b858 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -64,6 +64,8 @@ public class BluetoothUtils {
public static final int META_INT_ERROR = -1;
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
+ public static final String DEVELOPER_OPTION_PREVIEW_KEY =
+ "bluetooth_le_audio_sharing_ui_preview_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
private static final Set<Integer> SA_PROFILES =
@@ -643,6 +645,12 @@ public class BluetoothUtils {
&& connectedGroupIds.contains(groupId);
}
+ /** Returns if the le audio sharing UI is available. */
+ public static boolean isAudioSharingUIAvailable(@Nullable Context context) {
+ return isAudioSharingEnabled() || (context != null && isAudioSharingPreviewEnabled(
+ context.getContentResolver()));
+ }
+
/** Returns if the le audio sharing is enabled. */
public static boolean isAudioSharingEnabled() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -653,7 +661,23 @@ public class BluetoothUtils {
&& adapter.isLeAudioBroadcastAssistantSupported()
== BluetoothStatusCodes.FEATURE_SUPPORTED;
} catch (IllegalStateException e) {
- Log.d(TAG, "LE state is on, but there is no bluetooth service.", e);
+ Log.d(TAG, "Fail to check isAudioSharingEnabled, e = ", e);
+ return false;
+ }
+ }
+
+ /** Returns if the le audio sharing preview is enabled in developer option. */
+ public static boolean isAudioSharingPreviewEnabled(@Nullable ContentResolver contentResolver) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ try {
+ return Flags.audioSharingDeveloperOption()
+ && getAudioSharingPreviewValue(contentResolver)
+ && adapter.isLeAudioBroadcastSourceSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED
+ && adapter.isLeAudioBroadcastAssistantSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED;
+ } catch (IllegalStateException e) {
+ Log.d(TAG, "Fail to check isAudioSharingPreviewEnabled, e = ", e);
return false;
}
}
@@ -996,6 +1020,17 @@ public class BluetoothUtils {
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ /** Get develop option value for audio sharing preview. */
+ @WorkerThread
+ private static boolean getAudioSharingPreviewValue(@Nullable ContentResolver contentResolver) {
+ if (contentResolver == null) return false;
+ return Settings.Global.getInt(
+ contentResolver,
+ DEVELOPER_OPTION_PREVIEW_KEY,
+ 0 // value off
+ ) == 1;
+ }
+
/** Get secondary {@link CachedBluetoothDevice} in broadcast. */
@Nullable
@WorkerThread
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 8641f7036c50..d0827b30efc9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1245,7 +1245,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
*/
public String getConnectionSummary(boolean shortSummary) {
CharSequence summary = null;
- if (BluetoothUtils.isAudioSharingEnabled()) {
+ if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
if (mBluetoothManager == null) {
mBluetoothManager = LocalBluetoothManager.getInstance(mContext, null);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index b9f16edf6e77..4b7cb36f2753 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -383,11 +383,7 @@ public class CsipDeviceManager {
preferredMainDevice.refresh();
hasChanged = true;
}
- if (isWorkProfile()) {
- log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
- } else {
- syncAudioSharingSourceIfNeeded(preferredMainDevice);
- }
+ syncAudioSharingSourceIfNeeded(preferredMainDevice);
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -402,8 +398,12 @@ public class CsipDeviceManager {
}
private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
- boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingEnabled();
+ boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
if (isAudioSharingEnabled) {
+ if (isWorkProfile()) {
+ log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+ return;
+ }
boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
&& BluetoothUtils.hasConnectedBroadcastSource(
mainDevice, mBtManager);
@@ -433,6 +433,8 @@ public class CsipDeviceManager {
}
}
}
+ } else {
+ log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 0e060dfdd447..6d481dbe64e9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -29,11 +29,13 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -48,6 +50,7 @@ import android.util.Pair;
import com.android.internal.R;
import com.android.settingslib.flags.Flags;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.widget.AdaptiveIcon;
import com.google.common.collect.ImmutableList;
@@ -61,6 +64,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.Collections;
@@ -69,6 +74,7 @@ import java.util.List;
import java.util.Set;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothUtilsTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -88,6 +94,7 @@ public class BluetoothUtilsTest {
@Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private static final String STRING_METADATA = "string_metadata";
private static final String BOOL_METADATA = "true";
private static final String INT_METADATA = "25";
@@ -109,6 +116,7 @@ public class BluetoothUtilsTest {
mContext = spy(RuntimeEnvironment.application);
mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+ mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
@@ -1123,4 +1131,129 @@ public class BluetoothUtilsTest {
AudioDeviceInfo.TYPE_HEARING_AID,
address));
}
+
+ @Test
+ public void isAudioSharingEnabled_flagOff_returnsFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+
+ assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingEnabled_featureNotSupported_returnsFalse() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+ assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingEnabled_featureSupported_returnsTrue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+ assertThat(BluetoothUtils.isAudioSharingEnabled()).isTrue();
+ }
+
+ @Test
+ public void isAudioSharingPreviewEnabled_flagOff_returnsFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+ mContext.getContentResolver())).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingPreviewEnabled_featureNotSupported_returnsFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+ assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+ mContext.getContentResolver())).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingPreviewEnabled_developerOptionOff_returnsFalse() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0);
+
+ assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+ mContext.getContentResolver())).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingPreviewEnabled_developerOptionOn_returnsTrue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
+
+ assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+ mContext.getContentResolver())).isTrue();
+ }
+
+ @Test
+ public void isAudioSharingUIAvailable_audioSharingAndPreviewFlagOff_returnsFalse() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+ assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingUIAvailable_audioSharingAndPreviewDisabled_returnsFalse() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+ assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAudioSharingUIAvailable_audioSharingEnabled_returnsTrue() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0);
+
+ assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
+ }
+
+ @Test
+ public void isAudioSharingUIAvailable_audioSharingPreviewEnabled_returnsTrue() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
+
+ assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 064198fc5e46..927a1c59cc76 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -284,5 +284,6 @@ public class SecureSettings {
Settings.Secure.MANDATORY_BIOMETRICS,
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
Settings.Secure.ADVANCED_PROTECTION_MODE,
+ Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index c002a04d5b11..6d73ee27f076 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -332,6 +332,9 @@ public class SecureSettingsValidators {
VALIDATORS.put(
Secure.ACCESSIBILITY_QS_TARGETS,
ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
+ ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 2034f36c558b..fb0aaf8e5ae1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1823,6 +1823,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_QS_TARGETS,
SecureSettingsProto.Accessibility.QS_TARGETS);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
+ SecureSettingsProto.Accessibility.ACCESSIBILITY_KEY_GESTURE_TARGETS);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
SecureSettingsProto.Accessibility.ACCESSIBILITY_MAGNIFICATION_CAPABILITY);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2c8c261fa8f8..7b6321d1cc7d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -263,6 +263,8 @@
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
+ <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" />
+ <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
@@ -957,6 +959,13 @@
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+ <!-- Permission required for ExecutableMethodFileOffsetsTest -->
+ <uses-permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" />
+
+ <!-- Permissions required for CTS test - SettingsPreferenceServiceClientTest -->
+ <uses-permission android:name="android.permission.READ_SYSTEM_PREFERENCES" />
+ <uses-permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index b4d81d6937ed..7c478ac78a29 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -353,7 +353,7 @@ public class BugreportProgressService extends Service {
public void onDestroy() {
mServiceHandler.getLooper().quit();
mScreenshotHandler.getLooper().quit();
- mBugreportSingleThreadExecutor.close();
+ mBugreportSingleThreadExecutor.shutdown();
super.onDestroy();
}
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index 2910bba71341..635a97ef8a47 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -87,7 +87,7 @@ immediately for any callbacks added.
There are a few places where CommandQueue is used as a bus to communicate
across sysui. Such as when StatusBar calls CommandQueue#recomputeDisableFlags.
-This is generally used a shortcut to directly trigger CommandQueue rather than
+This is generally used as a shortcut to directly trigger CommandQueue rather than
calling StatusManager and waiting for the call to come back to IStatusBar.
### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5eae6d3e43fe..207ed71c955d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -16,6 +16,16 @@ flag {
}
flag {
+ name: "multiuser_wifi_picker_tracker_support"
+ namespace: "systemui"
+ description: "Adds WifiPickerTracker support for multiple users to support when HSUM is enabled."
+ bug: "371586248"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "udfps_view_performance"
namespace: "systemui"
description: "Decrease screen off blocking calls by waiting until the device is finished going to sleep before adding the udfps view."
@@ -631,9 +641,9 @@ flag {
}
flag {
- name: "status_bar_simple_fragment"
+ name: "status_bar_root_modernization"
namespace: "systemui"
- description: "Feature flag for refactoring the collapsed status bar fragment"
+ description: "Feature flag for replacing the status bar fragment with a compose root"
bug: "364360986"
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 00d905652987..300bdf2ffb01 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -48,7 +48,7 @@ class ViewHierarchyAnimator {
Bound.LEFT to createViewProperty(Bound.LEFT),
Bound.TOP to createViewProperty(Bound.TOP),
Bound.RIGHT to createViewProperty(Bound.RIGHT),
- Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
+ Bound.BOTTOM to createViewProperty(Bound.BOTTOM),
)
private fun createViewProperty(bound: Bound): IntProperty<View> {
@@ -89,7 +89,7 @@ class ViewHierarchyAnimator {
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
duration: Long = DEFAULT_DURATION,
animateChildren: Boolean = true,
- excludedViews: Set<View> = emptySet()
+ excludedViews: Set<View> = emptySet(),
): Boolean {
return animate(
rootView,
@@ -97,7 +97,7 @@ class ViewHierarchyAnimator {
duration,
ephemeral = false,
animateChildren = animateChildren,
- excludedViews = excludedViews
+ excludedViews = excludedViews,
)
}
@@ -111,7 +111,7 @@ class ViewHierarchyAnimator {
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
duration: Long = DEFAULT_DURATION,
animateChildren: Boolean = true,
- excludedViews: Set<View> = emptySet()
+ excludedViews: Set<View> = emptySet(),
): Boolean {
return animate(
rootView,
@@ -119,7 +119,7 @@ class ViewHierarchyAnimator {
duration,
ephemeral = true,
animateChildren = animateChildren,
- excludedViews = excludedViews
+ excludedViews = excludedViews,
)
}
@@ -129,7 +129,7 @@ class ViewHierarchyAnimator {
duration: Long,
ephemeral: Boolean,
animateChildren: Boolean,
- excludedViews: Set<View> = emptySet()
+ excludedViews: Set<View> = emptySet(),
): Boolean {
if (
!occupiesSpace(
@@ -137,7 +137,7 @@ class ViewHierarchyAnimator {
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
)
) {
return false
@@ -149,7 +149,7 @@ class ViewHierarchyAnimator {
listener,
recursive = true,
animateChildren = animateChildren,
- excludedViews = excludedViews
+ excludedViews = excludedViews,
)
return true
}
@@ -164,7 +164,7 @@ class ViewHierarchyAnimator {
private fun createUpdateListener(
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
): View.OnLayoutChangeListener {
return createListener(interpolator, duration, ephemeral)
}
@@ -196,9 +196,9 @@ class ViewHierarchyAnimator {
*
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
- * [includeFadeIn] is false.
- * @param onAnimationEnd an optional runnable that will be run once the animation
- * finishes successfully. Will not be run if the animation is cancelled.
+ * [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes,
+ * regardless of whether the animation is cancelled or finishes successfully.
*/
@JvmOverloads
fun animateAddition(
@@ -217,7 +217,7 @@ class ViewHierarchyAnimator {
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
)
) {
return false
@@ -241,7 +241,10 @@ class ViewHierarchyAnimator {
// First, fade in the container view
val containerDuration = duration / 6
createAndStartFadeInAnimator(
- rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
+ rootView,
+ containerDuration,
+ startDelay = 0,
+ interpolator = fadeInInterpolator,
)
// Then, fade in the child views
@@ -253,7 +256,7 @@ class ViewHierarchyAnimator {
childDuration,
// Wait until the container fades in before fading in the children
startDelay = containerDuration,
- interpolator = fadeInInterpolator
+ interpolator = fadeInInterpolator,
)
}
// For now, we don't recursively fade in additional sub views (e.g. grandchild
@@ -264,7 +267,7 @@ class ViewHierarchyAnimator {
rootView,
duration / 2,
startDelay = 0,
- interpolator = fadeInInterpolator
+ interpolator = fadeInInterpolator,
)
}
@@ -323,7 +326,7 @@ class ViewHierarchyAnimator {
previousLeft: Int,
previousTop: Int,
previousRight: Int,
- previousBottom: Int
+ previousBottom: Int,
) {
if (view == null) return
@@ -353,14 +356,14 @@ class ViewHierarchyAnimator {
startTop,
startRight,
startBottom,
- ignorePreviousValues
+ ignorePreviousValues,
)
val endValues =
mapOf(
Bound.LEFT to left,
Bound.TOP to top,
Bound.RIGHT to right,
- Bound.BOTTOM to bottom
+ Bound.BOTTOM to bottom,
)
val boundsToAnimate = mutableSetOf<Bound>()
@@ -396,8 +399,8 @@ class ViewHierarchyAnimator {
* added on the side(s) of the [destination], the translation of those margins can be
* included by specifying [includeMargins].
*
- * @param onAnimationEnd an optional runnable that will be run once the animation finishes
- * successfully. Will not be run if the animation is cancelled.
+ * @param onAnimationEnd an optional runnable that will be run once the animation finishes,
+ * regardless of whether the animation is cancelled or finishes successfully.
*/
@JvmOverloads
fun animateRemoval(
@@ -414,7 +417,7 @@ class ViewHierarchyAnimator {
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
)
) {
return false
@@ -458,7 +461,7 @@ class ViewHierarchyAnimator {
Bound.LEFT to rootView.left,
Bound.TOP to rootView.top,
Bound.RIGHT to rootView.right,
- Bound.BOTTOM to rootView.bottom
+ Bound.BOTTOM to rootView.bottom,
)
val endValues =
processEndValuesForRemoval(
@@ -550,7 +553,7 @@ class ViewHierarchyAnimator {
destination: Hotspot,
endValues: Map<Bound, Int>,
interpolator: Interpolator,
- duration: Long
+ duration: Long,
) {
for (i in 0 until rootView.childCount) {
val child = rootView.getChildAt(i)
@@ -559,7 +562,7 @@ class ViewHierarchyAnimator {
Bound.LEFT to child.left,
Bound.TOP to child.top,
Bound.RIGHT to child.right,
- Bound.BOTTOM to child.bottom
+ Bound.BOTTOM to child.bottom,
)
val childEndValues =
processChildEndValuesForRemoval(
@@ -569,7 +572,7 @@ class ViewHierarchyAnimator {
child.right,
child.bottom,
endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
- endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
+ endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP),
)
val boundsToAnimate = mutableSetOf<Bound>()
@@ -587,7 +590,7 @@ class ViewHierarchyAnimator {
childEndValues,
interpolator,
duration,
- ephemeral = true
+ ephemeral = true,
)
}
}
@@ -601,7 +604,7 @@ class ViewHierarchyAnimator {
left: Int,
top: Int,
right: Int,
- bottom: Int
+ bottom: Int,
): Boolean {
return visibility != View.GONE && left != right && top != bottom
}
@@ -616,6 +619,7 @@ class ViewHierarchyAnimator {
* not newly introduced margins are included.
*
* Base case
+ *
* ```
* 1) origin=TOP
* x---------x x---------x x---------x x---------x x---------x
@@ -636,9 +640,11 @@ class ViewHierarchyAnimator {
* x-----x x-------x | |
* x---------x
* ```
+ *
* In case the start and end values differ in the direction of the origin, and
* [ignorePreviousValues] is false, the previous values are used and a translation is
* included in addition to the view expansion.
+ *
* ```
* origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
* x
@@ -660,7 +666,7 @@ class ViewHierarchyAnimator {
previousTop: Int,
previousRight: Int,
previousBottom: Int,
- ignorePreviousValues: Boolean
+ ignorePreviousValues: Boolean,
): Map<Bound, Int> {
val startLeft = if (ignorePreviousValues) newLeft else previousLeft
val startTop = if (ignorePreviousValues) newTop else previousTop
@@ -727,7 +733,7 @@ class ViewHierarchyAnimator {
Bound.LEFT to left,
Bound.TOP to top,
Bound.RIGHT to right,
- Bound.BOTTOM to bottom
+ Bound.BOTTOM to bottom,
)
}
@@ -777,18 +783,17 @@ class ViewHierarchyAnimator {
includeMargins: Boolean = false,
): Map<Bound, Int> {
val marginAdjustment =
- if (includeMargins &&
- (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+ if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
DimenHolder(
left = marginLp.leftMargin,
top = marginLp.topMargin,
right = marginLp.rightMargin,
- bottom = marginLp.bottomMargin
+ bottom = marginLp.bottomMargin,
)
- } else {
- DimenHolder(0, 0, 0, 0)
- }
+ } else {
+ DimenHolder(0, 0, 0, 0)
+ }
// These are the end values to use *if* this bound is part of the destination.
val endLeft = left - marginAdjustment.left
@@ -805,60 +810,69 @@ class ViewHierarchyAnimator {
// - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
return when (destination) {
- Hotspot.TOP -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.TOP_RIGHT -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.RIGHT -> mapOf(
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.BOTTOM_RIGHT -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.RIGHT to endRight,
- Bound.LEFT to endRight,
- )
- Hotspot.BOTTOM -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to left,
- Bound.RIGHT to right,
- )
- Hotspot.BOTTOM_LEFT -> mapOf(
- Bound.BOTTOM to endBottom,
- Bound.TOP to endBottom,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.LEFT -> mapOf(
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- Bound.TOP to top,
- Bound.BOTTOM to bottom,
- )
- Hotspot.TOP_LEFT -> mapOf(
- Bound.TOP to endTop,
- Bound.BOTTOM to endTop,
- Bound.LEFT to endLeft,
- Bound.RIGHT to endLeft,
- )
- Hotspot.CENTER -> mapOf(
- Bound.LEFT to (endLeft + endRight) / 2,
- Bound.RIGHT to (endLeft + endRight) / 2,
- Bound.TOP to (endTop + endBottom) / 2,
- Bound.BOTTOM to (endTop + endBottom) / 2,
- )
+ Hotspot.TOP ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.TOP_RIGHT ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.RIGHT ->
+ mapOf(
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.BOTTOM_RIGHT ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.RIGHT to endRight,
+ Bound.LEFT to endRight,
+ )
+ Hotspot.BOTTOM ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to left,
+ Bound.RIGHT to right,
+ )
+ Hotspot.BOTTOM_LEFT ->
+ mapOf(
+ Bound.BOTTOM to endBottom,
+ Bound.TOP to endBottom,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.LEFT ->
+ mapOf(
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ Bound.TOP to top,
+ Bound.BOTTOM to bottom,
+ )
+ Hotspot.TOP_LEFT ->
+ mapOf(
+ Bound.TOP to endTop,
+ Bound.BOTTOM to endTop,
+ Bound.LEFT to endLeft,
+ Bound.RIGHT to endLeft,
+ )
+ Hotspot.CENTER ->
+ mapOf(
+ Bound.LEFT to (endLeft + endRight) / 2,
+ Bound.RIGHT to (endLeft + endRight) / 2,
+ Bound.TOP to (endTop + endBottom) / 2,
+ Bound.BOTTOM to (endTop + endBottom) / 2,
+ )
}
}
@@ -887,7 +901,7 @@ class ViewHierarchyAnimator {
right: Int,
bottom: Int,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Map<Bound, Int> {
val halfWidth = (right - left) / 2
val halfHeight = (bottom - top) / 2
@@ -945,7 +959,7 @@ class ViewHierarchyAnimator {
Bound.LEFT to endLeft,
Bound.TOP to endTop,
Bound.RIGHT to endRight,
- Bound.BOTTOM to endBottom
+ Bound.BOTTOM to endBottom,
)
}
@@ -954,7 +968,7 @@ class ViewHierarchyAnimator {
listener: View.OnLayoutChangeListener,
recursive: Boolean = false,
animateChildren: Boolean = true,
- excludedViews: Set<View> = emptySet()
+ excludedViews: Set<View> = emptySet(),
) {
if (excludedViews.contains(view)) return
@@ -973,7 +987,7 @@ class ViewHierarchyAnimator {
listener,
recursive = true,
animateChildren = animateChildren,
- excludedViews = excludedViews
+ excludedViews = excludedViews,
)
}
}
@@ -1027,7 +1041,7 @@ class ViewHierarchyAnimator {
PropertyValuesHolder.ofInt(
PROPERTIES[bound],
startValues.getValue(bound),
- endValues.getValue(bound)
+ endValues.getValue(bound),
)
)
}
@@ -1056,9 +1070,10 @@ class ViewHierarchyAnimator {
// listener.
recursivelyRemoveListener(view)
}
- if (!cancelled) {
- onAnimationEnd?.run()
- }
+ // Run the end runnable regardless of whether the animation was cancelled or
+ // not - this ensures critical actions (like removing a window) always occur
+ // (see b/344049884).
+ onAnimationEnd?.run()
}
override fun onAnimationCancel(animation: Animator) {
@@ -1077,17 +1092,19 @@ class ViewHierarchyAnimator {
view: View,
duration: Long,
startDelay: Long,
- interpolator: Interpolator
+ interpolator: Interpolator,
) {
val animator = ObjectAnimator.ofFloat(view, "alpha", 1f)
animator.startDelay = startDelay
animator.duration = duration
animator.interpolator = interpolator
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- view.setTag(R.id.tag_alpha_animator, null /* tag */)
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ view.setTag(R.id.tag_alpha_animator, null /* tag */)
+ }
}
- })
+ )
(view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
view.setTag(R.id.tag_alpha_animator, animator)
@@ -1105,7 +1122,7 @@ class ViewHierarchyAnimator {
RIGHT,
BOTTOM_RIGHT,
BOTTOM,
- BOTTOM_LEFT
+ BOTTOM_LEFT,
}
private enum class Bound(val label: String, val overrideTag: Int) {
@@ -1147,14 +1164,10 @@ class ViewHierarchyAnimator {
};
abstract fun setValue(view: View, value: Int)
+
abstract fun getValue(view: View): Int
}
/** Simple data class to hold a set of dimens for left, top, right, bottom. */
- private data class DimenHolder(
- val left: Int,
- val top: Int,
- val right: Int,
- val bottom: Int,
- )
+ private data class DimenHolder(val left: Int, val top: Int, val right: Int, val bottom: Int)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index d58e1bfbda83..163f4b36f472 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -59,6 +59,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -286,9 +287,12 @@ private fun DragHandle(dialog: Dialog) {
Surface(
modifier =
Modifier.padding(top = 16.dp, bottom = 6.dp)
- .semantics { contentDescription = dragHandleContentDescription }
+ .semantics {
+ contentDescription = dragHandleContentDescription
+ hideFromAccessibility()
+ }
.clickable { dialog.dismiss() },
- color = MaterialTheme.colorScheme.outlineVariant,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
shape = MaterialTheme.shapes.extraLarge,
) {
Box(Modifier.size(width = 32.dp, height = 4.dp))
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e7b66c5f0d2f..d976e8e62f3c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -693,8 +693,8 @@ private fun prepareInterruption(
val fromState = updateStateInContent(transition.fromContent)
val toState = updateStateInContent(transition.toContent)
- reconcileStates(element, previousTransition)
- reconcileStates(element, transition)
+ val previousUniqueState = reconcileStates(element, previousTransition, previousState = null)
+ reconcileStates(element, transition, previousState = previousUniqueState)
// Remove the interruption values to all contents but the content(s) where the element will be
// placed, to make sure that interruption deltas are computed only right after this interruption
@@ -721,12 +721,32 @@ private fun prepareInterruption(
/**
* Reconcile the state of [element] in the formContent and toContent of [transition] so that the
* values before interruption have their expected values, taking shared transitions into account.
+ *
+ * @return the unique state this element had during [transition], `null` if it had multiple
+ * different states (i.e. the shared animation was disabled).
*/
-private fun reconcileStates(element: Element, transition: TransitionState.Transition) {
- val fromContentState = element.stateByContent[transition.fromContent] ?: return
- val toContentState = element.stateByContent[transition.toContent] ?: return
+private fun reconcileStates(
+ element: Element,
+ transition: TransitionState.Transition,
+ previousState: Element.State?,
+): Element.State? {
+ fun reconcileWithPreviousState(state: Element.State) {
+ if (previousState != null && state.offsetBeforeInterruption == Offset.Unspecified) {
+ state.updateValuesBeforeInterruption(previousState)
+ }
+ }
+
+ val fromContentState = element.stateByContent[transition.fromContent]
+ val toContentState = element.stateByContent[transition.toContent]
+
+ if (fromContentState == null || toContentState == null) {
+ return (fromContentState ?: toContentState)
+ ?.also { reconcileWithPreviousState(it) }
+ ?.takeIf { it.offsetBeforeInterruption != Offset.Unspecified }
+ }
+
if (!isSharedElementEnabled(element.key, transition)) {
- return
+ return null
}
if (
@@ -735,13 +755,19 @@ private fun reconcileStates(element: Element, transition: TransitionState.Transi
) {
// Element is shared and placed in fromContent only.
toContentState.updateValuesBeforeInterruption(fromContentState)
- } else if (
+ return fromContentState
+ }
+
+ if (
toContentState.offsetBeforeInterruption != Offset.Unspecified &&
fromContentState.offsetBeforeInterruption == Offset.Unspecified
) {
// Element is shared and placed in toContent only.
fromContentState.updateValuesBeforeInterruption(toContentState)
+ return toContentState
}
+
+ return null
}
private fun Element.State.selfUpdateValuesBeforeInterruption() {
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 4a9051598ddc..a301856d024f 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
@@ -2638,4 +2638,58 @@ class ElementTest {
assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0)
}
}
+
+ @Test
+ fun interruption_considerPreviousUniqueState() {
+ @Composable
+ fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ Box(modifier.element(TestElements.Foo).size(50.dp))
+ }
+
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ scene(SceneC) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.offset(x = 100.dp, y = 100.dp)) }
+ }
+ }
+ }
+
+ // During A => B, Foo disappears and stays in its original position.
+ scope.launch { state.startTransition(transition(SceneA, SceneB)) }
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Interrupt A => B by B => C.
+ var interruptionProgress by mutableFloatStateOf(1f)
+ scope.launch {
+ state.startTransition(
+ transition(SceneB, SceneC, interruptionProgress = { interruptionProgress })
+ )
+ }
+
+ // During B => C, Foo appears again. It is still at (0, 0) when the interruption progress is
+ // 100%, and converges to its position (100, 100) in C.
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ interruptionProgress = 0.5f
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ interruptionProgress = 0f
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(100.dp, 100.dp)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index a8c3af9488f0..8db82d58ecc5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -24,8 +24,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
-class
-ViewHierarchyAnimatorTest : SysuiTestCase() {
+class ViewHierarchyAnimatorTest : SysuiTestCase() {
companion object {
private const val TEST_DURATION = 1000L
private val TEST_INTERPOLATOR = Interpolators.LINEAR
@@ -49,9 +48,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
// animate()
- var success = ViewHierarchyAnimator.animate(
- rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
- )
+ var success =
+ ViewHierarchyAnimator.animate(
+ rootView,
+ interpolator = TEST_INTERPOLATOR,
+ duration = TEST_DURATION,
+ )
rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -64,9 +66,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.stopAnimating(rootView)
// animateNextUpdate()
- success = ViewHierarchyAnimator.animateNextUpdate(
- rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
- )
+ success =
+ ViewHierarchyAnimator.animateNextUpdate(
+ rootView,
+ interpolator = TEST_INTERPOLATOR,
+ duration = TEST_DURATION,
+ )
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
assertTrue(success)
@@ -79,9 +84,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// animateAddition()
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ interpolator = TEST_INTERPOLATOR,
+ duration = TEST_DURATION,
+ )
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
assertTrue(success)
@@ -93,9 +101,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// animateRemoval()
setUpRootWithChildren()
val child = rootView.getChildAt(0)
- success = ViewHierarchyAnimator.animateRemoval(
- child, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ child,
+ interpolator = TEST_INTERPOLATOR,
+ duration = TEST_DURATION,
+ )
assertTrue(success)
assertNotNull(child.getTag(R.id.tag_animator))
@@ -185,7 +196,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// Change all bounds.
rootView.measure(
View.MeasureSpec.makeMeasureSpec(190, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
)
rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
@@ -211,14 +222,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesRootAndChildren_withExcludedViews() {
setUpRootWithChildren()
- val success = ViewHierarchyAnimator.animate(
- rootView,
- excludedViews = setOf(rootView.getChildAt(0))
- )
+ val success =
+ ViewHierarchyAnimator.animate(rootView, excludedViews = setOf(rootView.getChildAt(0)))
// Change all bounds.
rootView.measure(
- View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
)
rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
@@ -245,14 +254,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesRootOnly() {
setUpRootWithChildren()
- val success = ViewHierarchyAnimator.animate(
- rootView,
- animateChildren = false
- )
+ val success = ViewHierarchyAnimator.animate(rootView, animateChildren = false)
// Change all bounds.
rootView.measure(
- View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
)
rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
@@ -351,10 +357,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesAppearingViewsRespectingOrigin() {
// CENTER.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- var success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.CENTER
- )
+ var success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -364,10 +371,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// LEFT.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.LEFT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -377,10 +385,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP_LEFT.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -390,10 +399,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP.
rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.TOP
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -403,10 +413,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP_RIGHT.
rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -416,10 +427,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// RIGHT.
rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.RIGHT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -429,10 +441,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM_RIGHT.
rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -442,10 +455,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM.
rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -455,10 +469,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM_LEFT.
rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -471,11 +486,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
fun animatesAppearingViewsRespectingMargins() {
// CENTER.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- var success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.CENTER,
- includeMargins = true
- )
+ var success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -485,10 +501,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// LEFT.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView, origin = ViewHierarchyAnimator.Hotspot.LEFT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.LEFT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -498,11 +516,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP_LEFT.
rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -512,10 +531,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP.
rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView, origin = ViewHierarchyAnimator.Hotspot.TOP,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -525,11 +546,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// TOP_RIGHT.
rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -539,11 +561,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// RIGHT.
rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.RIGHT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.RIGHT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -553,11 +576,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM_RIGHT.
rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -567,11 +591,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM.
rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -581,11 +606,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// BOTTOM_LEFT.
rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
- success = ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
- includeMargins = true
- )
+ success =
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ includeMargins = true,
+ )
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
assertTrue(success)
@@ -626,7 +652,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = true,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -641,7 +667,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = true,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -663,7 +689,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = true,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -680,7 +706,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = true,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -692,7 +718,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = true,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
// THEN the alpha remains at its current value (it doesn't get reset to 0)
@@ -721,7 +747,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.animateAddition(
rootView,
includeFadeIn = false,
- fadeInInterpolator = Interpolators.LINEAR
+ fadeInInterpolator = Interpolators.LINEAR,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -738,10 +764,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val onAnimationEndRunnable = { runnableRun = true }
ViewHierarchyAnimator.animateAddition(
- rootView,
- origin = ViewHierarchyAnimator.Hotspot.CENTER,
- includeMargins = true,
- onAnimationEnd = onAnimationEndRunnable
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -751,7 +777,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
- fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+ fun animateAddition_runnableRunsWhenAnimationCancelled() {
var runnableRun = false
val onAnimationEndRunnable = { runnableRun = true }
@@ -759,13 +785,13 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
rootView,
origin = ViewHierarchyAnimator.Hotspot.CENTER,
includeMargins = true,
- onAnimationEnd = onAnimationEndRunnable
+ onAnimationEnd = onAnimationEndRunnable,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
cancelAnimation(rootView)
- assertEquals(false, runnableRun)
+ assertEquals(true, runnableRun)
}
@Test
@@ -777,7 +803,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
rootView,
origin = ViewHierarchyAnimator.Hotspot.CENTER,
includeMargins = true,
- onAnimationEnd = onAnimationEndRunnable
+ onAnimationEnd = onAnimationEndRunnable,
)
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
@@ -791,11 +817,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
val child = rootView.getChildAt(0)
- val success = ViewHierarchyAnimator.animateRemoval(
- child,
- destination = ViewHierarchyAnimator.Hotspot.LEFT,
- interpolator = Interpolators.LINEAR
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ child,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ interpolator = Interpolators.LINEAR,
+ )
assertTrue(success)
assertNotNull(child.getTag(R.id.tag_animator))
@@ -820,11 +847,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
rootView.addView(onlyChild)
forceLayout()
- val success = ViewHierarchyAnimator.animateRemoval(
- onlyChild,
- destination = ViewHierarchyAnimator.Hotspot.LEFT,
- interpolator = Interpolators.LINEAR
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ onlyChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ interpolator = Interpolators.LINEAR,
+ )
assertTrue(success)
assertNotNull(onlyChild.getTag(R.id.tag_animator))
@@ -845,9 +873,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
var removedChild = rootView.getChildAt(0)
var remainingChild = rootView.getChildAt(1)
- var success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.CENTER
- )
+ var success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.CENTER,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -863,9 +893,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -881,9 +913,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -899,9 +933,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -917,9 +953,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -935,9 +973,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -953,9 +993,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -971,9 +1013,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -989,9 +1033,11 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
setUpRootWithChildren()
removedChild = rootView.getChildAt(0)
remainingChild = rootView.getChildAt(1)
- success = ViewHierarchyAnimator.animateRemoval(
- removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT
- )
+ success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ )
// Ensure that the layout happens before the checks.
forceLayout()
@@ -1014,11 +1060,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalRight = removedChild.right
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.CENTER,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1027,13 +1074,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2
- checkBounds(
- removedChild,
- l = expectedX,
- t = expectedY,
- r = expectedX,
- b = expectedY
- )
+ checkBounds(removedChild, l = expectedX, t = expectedY, r = expectedX, b = expectedY)
}
@Test
@@ -1044,11 +1085,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalTop = removedChild.top
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.LEFT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.LEFT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1059,7 +1101,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalLeft - M_LEFT,
t = originalTop,
r = originalLeft - M_LEFT,
- b = originalBottom
+ b = originalBottom,
)
}
@@ -1070,11 +1112,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalLeft = removedChild.left
val originalTop = removedChild.top
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1085,7 +1128,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalLeft - M_LEFT,
t = originalTop - M_TOP,
r = originalLeft - M_LEFT,
- b = originalTop - M_TOP
+ b = originalTop - M_TOP,
)
}
@@ -1097,11 +1140,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalTop = removedChild.top
val originalRight = removedChild.right
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.TOP,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1112,7 +1156,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalLeft,
t = originalTop - M_TOP,
r = originalRight,
- b = originalTop - M_TOP
+ b = originalTop - M_TOP,
)
}
@@ -1123,11 +1167,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalTop = removedChild.top
val originalRight = removedChild.right
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1138,7 +1183,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalRight + M_RIGHT,
t = originalTop - M_TOP,
r = originalRight + M_RIGHT,
- b = originalTop - M_TOP
+ b = originalTop - M_TOP,
)
}
@@ -1150,11 +1195,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalRight = removedChild.right
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.RIGHT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1165,7 +1211,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalRight + M_RIGHT,
t = originalTop,
r = originalRight + M_RIGHT,
- b = originalBottom
+ b = originalBottom,
)
}
@@ -1176,11 +1222,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalRight = removedChild.right
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1191,7 +1238,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalRight + M_RIGHT,
t = originalBottom + M_BOTTOM,
r = originalRight + M_RIGHT,
- b = originalBottom + M_BOTTOM
+ b = originalBottom + M_BOTTOM,
)
}
@@ -1203,11 +1250,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalRight = removedChild.right
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1218,7 +1266,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalLeft,
t = originalBottom + M_BOTTOM,
r = originalRight,
- b = originalBottom + M_BOTTOM
+ b = originalBottom + M_BOTTOM,
)
}
@@ -1229,11 +1277,12 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val originalLeft = removedChild.left
val originalBottom = removedChild.bottom
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild,
- destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
- includeMargins = true,
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(
+ removedChild,
+ destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+ includeMargins = true,
+ )
forceLayout()
assertTrue(success)
@@ -1244,9 +1293,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
l = originalLeft - M_LEFT,
t = originalBottom + M_BOTTOM,
r = originalLeft - M_LEFT,
- b = originalBottom + M_BOTTOM
+ b = originalBottom + M_BOTTOM,
)
}
+
/* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */
@Test
@@ -1256,9 +1306,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val child = rootView.getChildAt(0) as ViewGroup
val firstGrandChild = child.getChildAt(0)
val secondGrandChild = child.getChildAt(1)
- val success = ViewHierarchyAnimator.animateRemoval(
- child, interpolator = Interpolators.LINEAR
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(child, interpolator = Interpolators.LINEAR)
assertTrue(success)
assertNotNull(child.getTag(R.id.tag_animator))
@@ -1288,9 +1337,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val removedChild = rootView.getChildAt(0)
val remainingChild = rootView.getChildAt(1)
- val success = ViewHierarchyAnimator.animateRemoval(
- removedChild, interpolator = Interpolators.LINEAR
- )
+ val success =
+ ViewHierarchyAnimator.animateRemoval(removedChild, interpolator = Interpolators.LINEAR)
// Ensure that the layout happens before the checks.
forceLayout()
@@ -1315,17 +1363,14 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
forceLayout()
val removedView = rootView.getChildAt(0)
- ViewHierarchyAnimator.animateRemoval(
- removedView,
- onAnimationEnd = onAnimationEndRunnable
- )
+ ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
endAnimation(removedView)
assertEquals(true, runnableRun)
}
@Test
- fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
+ fun animateRemoval_runnableRunsWhenAnimationCancelled() {
var runnableRun = false
val onAnimationEndRunnable = { runnableRun = true }
@@ -1333,13 +1378,10 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
forceLayout()
val removedView = rootView.getChildAt(0)
- ViewHierarchyAnimator.animateRemoval(
- removedView,
- onAnimationEnd = onAnimationEndRunnable
- )
+ ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
cancelAnimation(removedView)
- assertEquals(false, runnableRun)
+ assertEquals(true, runnableRun)
}
@Test
@@ -1351,10 +1393,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
forceLayout()
val removedView = rootView.getChildAt(0)
- ViewHierarchyAnimator.animateRemoval(
- removedView,
- onAnimationEnd = onAnimationEndRunnable
- )
+ ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
advanceAnimation(removedView, 0.5f)
assertEquals(false, runnableRun)
@@ -1370,7 +1409,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
rootView.addView(secondChild)
rootView.measure(
View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
)
rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
@@ -1378,7 +1417,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
// Change all bounds.
rootView.measure(
View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
)
rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
@@ -1501,7 +1540,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
// Change all bounds again.
- rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
assertNull(rootView.getTag(R.id.tag_animator))
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -1523,7 +1562,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
ViewHierarchyAnimator.stopAnimating(rootView)
// Change all bounds again.
- rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
assertNull(rootView.getTag(R.id.tag_animator))
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -1543,10 +1582,8 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val secondChild = View(mContext)
rootView.addView(secondChild)
- val firstChildParams = LinearLayout.LayoutParams(
- 0 /* width */,
- LinearLayout.LayoutParams.MATCH_PARENT
- )
+ val firstChildParams =
+ LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT)
firstChildParams.weight = 0.5f
if (includeMarginsOnFirstChild) {
firstChildParams.leftMargin = M_LEFT
@@ -1556,23 +1593,25 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
firstChild.layoutParams = firstChildParams
- val secondChildParams = LinearLayout.LayoutParams(
- 0 /* width */,
- LinearLayout.LayoutParams.MATCH_PARENT
- )
+ val secondChildParams =
+ LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT)
secondChildParams.weight = 0.5f
secondChild.layoutParams = secondChildParams
firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
- (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
- .addRule(RelativeLayout.ALIGN_PARENT_START)
- (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
- .addRule(RelativeLayout.ALIGN_PARENT_TOP)
+ (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+ RelativeLayout.ALIGN_PARENT_START
+ )
+ (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+ RelativeLayout.ALIGN_PARENT_TOP
+ )
secondGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
- (secondGrandChild.layoutParams as RelativeLayout.LayoutParams)
- .addRule(RelativeLayout.ALIGN_PARENT_END)
- (secondGrandChild.layoutParams as RelativeLayout.LayoutParams)
- .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+ (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+ RelativeLayout.ALIGN_PARENT_END
+ )
+ (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+ RelativeLayout.ALIGN_PARENT_BOTTOM
+ )
forceLayout()
}
@@ -1580,7 +1619,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
private fun forceLayout() {
rootView.measure(
View.MeasureSpec.makeMeasureSpec(200 /* width */, View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST)
+ View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST),
)
rootView.layout(0 /* l */, 0 /* t */, 200 /* r */, 100 /* b */)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 160865d625f5..81d3f7232c78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,11 +31,8 @@ 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.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -243,16 +240,11 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
}
@Test
- fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+ fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
testScope.runTest {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- testScope = this,
- )
kosmos.powerInteractor.setAsleepForTest()
runCurrent()
@@ -266,26 +258,6 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
}
@Test
- fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
- testScope.runTest {
- val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
- .isEqualTo(KeyguardState.LOCKSCREEN)
-
- kosmos.powerInteractor.setAsleepForTest()
- runCurrent()
-
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- }
-
- @Test
fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
testScope.runTest {
kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
index 8e67e602abd9..f8f6fe246563 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.fakeSessionTokenFactory
import com.android.systemui.res.R
import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.execution
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
@@ -105,6 +106,7 @@ class Media3ActionFactoryTest : SysuiTestCase() {
kosmos.looper,
handler,
kosmos.testScope,
+ kosmos.execution,
)
controllerFactory.setMedia3Controller(media3Controller)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 8ccaf6bc0651..0f631509bfba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -300,7 +300,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
- assertThat(mController.isEnabledForLockScreenButton()).isFalse();
+ assertThat(mController.isEnabledForLockScreenButton()).isTrue();
assertThat(mController.isAllowedOnLockScreen()).isTrue();
assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 5cba325450e6..03feceb7c15a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -318,6 +318,63 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
}
}
+ @Test
+ fun qqsMediaExpansion_collapsedMediaInLandscape() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ setCollapsedMediaInLandscape(true)
+ setMediaState(ACTIVE_MEDIA)
+
+ setConfigurationForMediaInRow(mediaInRow = false)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+ setConfigurationForMediaInRow(mediaInRow = true)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
+ }
+ }
+
+ @Test
+ fun qqsMediaExpansion_notCollapsedMediaInLandscape_alwaysExpanded() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ setCollapsedMediaInLandscape(false)
+ setMediaState(ACTIVE_MEDIA)
+
+ setConfigurationForMediaInRow(mediaInRow = false)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+ setConfigurationForMediaInRow(mediaInRow = true)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+ }
+ }
+
+ @Test
+ fun qqsMediaExpansion_reactsToChangesInCollapsedMediaInLandscape() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ setConfigurationForMediaInRow(mediaInRow = true)
+ setMediaState(ACTIVE_MEDIA)
+
+ setCollapsedMediaInLandscape(false)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+ setCollapsedMediaInLandscape(true)
+ Snapshot.sendApplyNotifications()
+ runCurrent()
+ assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
+ }
+ }
+
private fun TestScope.setMediaState(state: MediaState) {
with(kosmos) {
val activeMedia = state == ACTIVE_MEDIA
@@ -331,6 +388,14 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest()
runCurrent()
}
+ private fun TestScope.setCollapsedMediaInLandscape(collapsed: Boolean) {
+ with(kosmos) {
+ overrideResource(R.bool.config_quickSettingsMediaLandscapeCollapsed, collapsed)
+ fakeConfigurationRepository.onAnyConfigurationChange()
+ }
+ runCurrent()
+ }
+
companion object {
private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index dda9cd5529a5..4dcbdfae6d4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -104,67 +104,6 @@ class QSPreferencesRepositoryTest : SysuiTestCase() {
}
}
- @Test
- fun showLabels_updatesFromSharedPreferences() =
- with(kosmos) {
- testScope.runTest {
- val latest by collectLastValue(underTest.showLabels)
- assertThat(latest).isFalse()
-
- setShowLabelsInSharedPreferences(true)
- assertThat(latest).isTrue()
-
- setShowLabelsInSharedPreferences(false)
- assertThat(latest).isFalse()
- }
- }
-
- @Test
- fun showLabels_updatesFromUserChange() =
- with(kosmos) {
- testScope.runTest {
- fakeUserRepository.setUserInfos(USERS)
- val latest by collectLastValue(underTest.showLabels)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- setShowLabelsInSharedPreferences(false)
-
- fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
- setShowLabelsInSharedPreferences(true)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- assertThat(latest).isFalse()
- }
- }
-
- @Test
- fun setShowLabels_inSharedPreferences() {
- underTest.setShowLabels(false)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
- underTest.setShowLabels(true)
- assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
- }
-
- @Test
- fun setShowLabels_forDifferentUser() =
- with(kosmos) {
- testScope.runTest {
- fakeUserRepository.setUserInfos(USERS)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- underTest.setShowLabels(false)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
- fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
- underTest.setShowLabels(true)
- assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
- }
- }
-
private fun getSharedPreferences(): SharedPreferences =
with(kosmos) {
return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 55f88cc5b7a2..08b996146f2c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -753,7 +753,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
lastSleepReason = WakeSleepReason.POWER_BUTTON,
powerButtonLaunchGestureTriggered = false,
)
- transitionStateFlow.value = Transition(from = Scenes.Shade, to = Scenes.Lockscreen)
+ transitionStateFlow.value = Transition(from = Scenes.Gone, to = Scenes.Lockscreen)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
kosmos.fakePowerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
new file mode 100644
index 000000000000..a9a5cac6112e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadePositionRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val commandRegistry = kosmos.commandRegistry
+ private val pw = PrintWriter(StringWriter())
+
+ private val underTest = ShadePositionRepositoryImpl(commandRegistry)
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
+ @Test
+ fun commandDisplayOverride_updatesDisplayId() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ val newDisplayId = 2
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("shade_display_override", newDisplayId.toString()),
+ )
+
+ assertThat(displayId).isEqualTo(newDisplayId)
+ }
+
+ @Test
+ fun commandShadeDisplayOverride_resetsDisplayId() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ val newDisplayId = 2
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("shade_display_override", newDisplayId.toString()),
+ )
+ assertThat(displayId).isEqualTo(newDisplayId)
+
+ commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", "reset"))
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 643acdbb9277..2a3878c17a1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.statusbar.connectivity
+import android.content.Context
+import android.os.UserHandle
import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,36 +39,28 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyList
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class AccessPointControllerImplTest : SysuiTestCase() {
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var wifiPickerTrackerFactory:
- WifiPickerTrackerFactory
- @Mock
- private lateinit var wifiPickerTracker: WifiPickerTracker
- @Mock
- private lateinit var callback: AccessPointController.AccessPointCallback
- @Mock
- private lateinit var otherCallback: AccessPointController.AccessPointCallback
- @Mock
- private lateinit var wifiEntryConnected: WifiEntry
- @Mock
- private lateinit var wifiEntryOther: WifiEntry
- @Captor
- private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock private lateinit var callback: AccessPointController.AccessPointCallback
+ @Mock private lateinit var otherCallback: AccessPointController.AccessPointCallback
+ @Mock private lateinit var wifiEntryConnected: WifiEntry
+ @Mock private lateinit var wifiEntryOther: WifiEntry
+ @Captor private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
private val instantExecutor = Executor { it.run() }
private lateinit var controller: AccessPointControllerImpl
@@ -72,19 +68,21 @@ class AccessPointControllerImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+ `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+ .thenReturn(wifiPickerTracker)
`when`(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntryConnected)
- `when`(wifiPickerTracker.wifiEntries).thenReturn(ArrayList<WifiEntry>().apply {
- add(wifiEntryOther)
- })
+ `when`(wifiPickerTracker.wifiEntries)
+ .thenReturn(ArrayList<WifiEntry>().apply { add(wifiEntryOther) })
- controller = AccessPointControllerImpl(
+ controller =
+ AccessPointControllerImpl(
+ mContext,
userManager,
userTracker,
instantExecutor,
- wifiPickerTrackerFactory
- )
+ wifiPickerTrackerFactory,
+ )
controller.init()
}
@@ -183,13 +181,15 @@ class AccessPointControllerImplTest : SysuiTestCase() {
@Test
fun testReturnEmptyListWhenNoWifiPickerTracker() {
- `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(null)
- val otherController = AccessPointControllerImpl(
+ `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any())).thenReturn(null)
+ val otherController =
+ AccessPointControllerImpl(
+ mContext,
userManager,
userTracker,
instantExecutor,
- wifiPickerTrackerFactory
- )
+ wifiPickerTrackerFactory,
+ )
otherController.init()
otherController.addAccessPointCallback(callback)
@@ -244,4 +244,19 @@ class AccessPointControllerImplTest : SysuiTestCase() {
verify(wifiEntryOther).connect(any())
verify(callback, never()).onSettingsActivityTriggered(any())
}
+
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun switchUsers() {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
+ controller.onUserSwitched(PRIMARY_USER_ID)
+ // Create is expected to be called once when the test starts and a second time when the user
+ // is switched.
+ verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ }
+
+ private companion object {
+ private const val PRIMARY_USER_ID = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 938da88a8819..009b33b9f808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -23,7 +23,6 @@ import android.platform.test.annotations.EnableFlags
import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -81,21 +80,21 @@ class StatusBarInitializerTest : SysuiTestCase() {
)
@Test
- @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
- fun simpleFragment_startsFromCoreStartable() {
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+ fun flagOn_startsFromCoreStartable() {
underTest.start()
assertThat(underTest.initialized).isTrue()
}
@Test
- @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
- fun simpleFragment_throwsIfInitializeIsCalled() {
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+ fun flagOn_throwsIfInitializeIsCalled() {
assertThrows(IllegalStateException::class.java) { underTest.initializeStatusBar() }
}
@Test
- @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
- fun simpleFragment_flagEnabled_doesNotCreateFragment() {
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+ fun flagOn_flagEnabled_doesNotCreateFragment() {
underTest.start()
verify(fragmentManager, never()).beginTransaction()
@@ -103,14 +102,14 @@ class StatusBarInitializerTest : SysuiTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
fun flagOff_startCalled_stillInitializes() {
underTest.start()
assertThat(underTest.initialized).isTrue()
}
@Test
- @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
fun flagOff_doesNotThrowIfInitializeIsCalled() {
underTest.initializeStatusBar()
assertThat(underTest.initialized).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index dae5542123ed..50db9f7268e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -21,16 +21,19 @@ import android.service.notification.StatusBarNotification
import android.view.View.VISIBLE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
@@ -52,8 +55,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var stackLayout: NotificationStackScrollLayout
+ @Mock private lateinit var seenNotificationsInteractor: SeenNotificationsInteractor
private val testableResources = mContext.orCreateTestableResources
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var sizeCalculator: NotificationStackSizeCalculator
@@ -72,7 +78,9 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
lockscreenShadeTransitionController = lockscreenShadeTransitionController,
mediaDataManager = mediaDataManager,
testableResources.resources,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ seenNotificationsInteractor = seenNotificationsInteractor,
+ scope = testScope,
)
}
@@ -85,7 +93,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications = 0f,
spaceForShelf = 0f,
- shelfHeight = 0f
+ shelfHeight = 0f,
)
assertThat(maxNotifications).isEqualTo(0)
@@ -101,7 +109,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications = Float.MAX_VALUE,
spaceForShelf = Float.MAX_VALUE,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(numberOfRows)
@@ -137,7 +145,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
listOf(row),
/* spaceForNotifications= */ 5f,
/* spaceForShelf= */ 0f,
- /* shelfHeight= */ 0f
+ /* shelfHeight= */ 0f,
)
assertThat(maxNotifications).isEqualTo(1)
@@ -148,11 +156,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
setGapHeight(gapHeight)
val shelfHeight = shelfHeight + dividerHeight
val spaceForNotifications =
- listOf(
- rowHeight + dividerHeight,
- gapHeight + rowHeight + dividerHeight,
- )
- .sum()
+ listOf(rowHeight + dividerHeight, gapHeight + rowHeight + dividerHeight).sum()
val spaceForShelf = gapHeight + dividerHeight + shelfHeight
val rows =
listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
@@ -162,7 +166,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications + 1,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(2)
@@ -173,12 +177,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
// Each row in separate section.
setGapHeight(gapHeight)
- val notifSpace =
- listOf(
- rowHeight,
- dividerHeight + gapHeight + rowHeight,
- )
- .sum()
+ val notifSpace = listOf(rowHeight, dividerHeight + gapHeight + rowHeight).sum()
val shelfSpace = dividerHeight + gapHeight + shelfHeight
val spaceUsed = notifSpace + shelfSpace
@@ -209,7 +208,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications + 1,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(1)
@@ -252,7 +251,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenEnoughSpace).isEqualTo(10f)
}
@@ -272,7 +271,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenEnoughSpace).isEqualTo(5)
}
@@ -291,7 +290,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenSavingSpace).isEqualTo(5)
}
@@ -311,7 +310,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenSavingSpace).isEqualTo(5)
}
@@ -330,7 +329,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = false
+ onLockscreen = false,
)
assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
@@ -340,14 +339,14 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows: List<ExpandableView>,
spaceForNotifications: Float,
spaceForShelf: Float,
- shelfHeight: Float = this.shelfHeight
+ shelfHeight: Float = this.shelfHeight,
): Int {
setupChildren(rows)
return sizeCalculator.computeMaxKeyguardNotifications(
stackLayout,
spaceForNotifications,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index a8bcfbcfc539..39a1c106cfcf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.telephony.CellSignalStrength
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
@@ -735,9 +736,10 @@ class MobileIconInteractorTest : SysuiTestCase() {
}
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
// See b/346904529 for more context
- fun satBasedIcon_doesNotInflateSignalStrength() =
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOff() =
testScope.runTest {
val latest by collectLastValue(underTest.signalLevelIcon)
@@ -756,7 +758,75 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(4)
}
+ @EnableFlags(
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+ )
+ @Test
+ // See b/346904529 for more context
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.satelliteLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_usesPrimaryLevel_flagOff() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+
+ // GIVEN primary level is set
+ connectionRepository.primaryLevel.value = 4
+ connectionRepository.satelliteLevel.value = 0
+
+ // THEN icon uses the primary level because the flag is off
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ @EnableFlags(
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+ )
+ @Test
+ fun satBasedIcon_usesSatelliteLevel_flagOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+
+ // GIVEN satellite level is set
+ connectionRepository.satelliteLevel.value = 4
+ connectionRepository.primaryLevel.value = 0
+
+ // THEN icon uses the satellite level because the flag is on
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ /**
+ * Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
+ * rolled out. The new API should report 0 automatically if not in service.
+ */
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 4c7cdfa7fb67..038722cd9608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -151,7 +151,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
iconsInteractor.isForceHidden,
repository,
context,
- MobileIconCarrierIdOverridesFake()
+ MobileIconCarrierIdOverridesFake(),
)
createAndSetViewModel()
}
@@ -359,7 +359,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
connectionsRepository.mobileIsDefault.value = true
repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
@@ -406,7 +406,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
repository.setDataEnabled(true)
@@ -426,7 +426,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val initial =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
repository.setNetworkTypeKey(connectionsRepository.GSM_KEY)
@@ -448,7 +448,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
repository.dataEnabled.value = true
var latest: Icon? = null
@@ -477,7 +477,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
assertThat(latest).isEqualTo(expected)
@@ -499,7 +499,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
assertThat(latest).isEqualTo(expected)
@@ -520,7 +520,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
assertThat(latest).isEqualTo(expected)
@@ -542,7 +542,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
connectionsRepository.defaultMobileIconGroup.value.dataType,
- ContentDescription.Resource(G.dataContentDescription)
+ ContentDescription.Resource(G.dataContentDescription),
)
assertThat(latest).isEqualTo(expected)
@@ -564,7 +564,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
val expected =
Icon.Resource(
THREE_G.dataType,
- ContentDescription.Resource(THREE_G.dataContentDescription)
+ ContentDescription.Resource(THREE_G.dataContentDescription),
)
assertThat(latest).isEqualTo(expected)
@@ -621,10 +621,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = true,
- )
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
assertThat(inVisible).isFalse()
assertThat(outVisible).isFalse()
@@ -654,10 +651,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = false,
- )
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
yield()
@@ -666,20 +660,14 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(containerVisible).isTrue()
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = true,
- )
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
assertThat(inVisible).isFalse()
assertThat(outVisible).isTrue()
assertThat(containerVisible).isTrue()
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- )
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
assertThat(inVisible).isFalse()
assertThat(outVisible).isFalse()
@@ -709,10 +697,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = false,
- )
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
yield()
@@ -721,20 +706,14 @@ class MobileIconViewModelTest : SysuiTestCase() {
assertThat(containerVisible).isTrue()
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = true,
- )
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
assertThat(inVisible).isFalse()
assertThat(outVisible).isTrue()
assertThat(containerVisible).isTrue()
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- )
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
assertThat(inVisible).isFalse()
assertThat(outVisible).isFalse()
@@ -824,10 +803,7 @@ class MobileIconViewModelTest : SysuiTestCase() {
// sets the background on cellular
repository.hasPrioritizedNetworkCapabilities.value = true
repository.dataActivityDirection.value =
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = true,
- )
+ DataActivityModel(hasActivityIn = true, hasActivityOut = true)
assertThat(roaming).isFalse()
assertThat(networkTypeIcon).isNull()
@@ -838,11 +814,13 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun nonTerrestrial_usesSatelliteIcon() =
+ fun nonTerrestrial_usesSatelliteIcon_flagOff() =
testScope.runTest {
repository.isNonTerrestrial.value = true
repository.setAllLevels(0)
+ repository.satelliteLevel.value = 0
val latest by
collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
@@ -853,28 +831,66 @@ class MobileIconViewModelTest : SysuiTestCase() {
// 1-2 -> 1 bar
repository.setAllLevels(1)
+ repository.satelliteLevel.value = 1
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
repository.setAllLevels(2)
+ repository.satelliteLevel.value = 2
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
// 3-4 -> 2 bars
repository.setAllLevels(3)
+ repository.satelliteLevel.value = 3
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
repository.setAllLevels(4)
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ @EnableFlags(
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+ )
+ @Test
+ fun nonTerrestrial_usesSatelliteIcon_flagOn() =
+ testScope.runTest {
+ repository.isNonTerrestrial.value = true
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.satelliteLevel.value = 4
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
@EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@Test
- fun satelliteIcon_ignoresInflateSignalStrength() =
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOff() =
testScope.runTest {
// Note that this is the exact same test as above, but with inflateSignalStrength set to
// true we note that the level is unaffected by inflation
repository.inflateSignalStrength.value = true
repository.isNonTerrestrial.value = true
repository.setAllLevels(0)
+ repository.satelliteLevel.value = 0
val latest by
collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
@@ -885,16 +901,55 @@ class MobileIconViewModelTest : SysuiTestCase() {
// 1-2 -> 1 bar
repository.setAllLevels(1)
+ repository.satelliteLevel.value = 1
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
repository.setAllLevels(2)
+ repository.satelliteLevel.value = 2
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
// 3-4 -> 2 bars
repository.setAllLevels(3)
+ repository.satelliteLevel.value = 3
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
repository.setAllLevels(4)
+ repository.satelliteLevel.value = 4
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+ }
+
+ @EnableFlags(
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG,
+ com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN,
+ )
+ @Test
+ fun satelliteIcon_ignoresInflateSignalStrength_flagOn() =
+ testScope.runTest {
+ // Note that this is the exact same test as above, but with inflateSignalStrength set to
+ // true we note that the level is unaffected by inflation
+ repository.inflateSignalStrength.value = true
+ repository.isNonTerrestrial.value = true
+ repository.satelliteLevel.value = 0
+
+ val latest by
+ collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class))
+
+ // Level 0 -> no connection
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0)
+
+ // 1-2 -> 1 bar
+ repository.satelliteLevel.value = 1
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ repository.satelliteLevel.value = 2
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1)
+
+ // 3-4 -> 2 bars
+ repository.satelliteLevel.value = 3
+ assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
+
+ repository.satelliteLevel.value = 4
assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index b5dbc3fe1b4d..33223aef11ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoMod
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -71,6 +72,7 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val userRepository = FakeUserRepository()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -82,10 +84,13 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
- whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+ whenever(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+ .thenReturn(wifiPickerTracker)
realImpl =
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
mainExecutor,
testDispatcher,
@@ -97,11 +102,7 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
- demoImpl =
- DemoWifiRepository(
- demoModeWifiDataSource,
- testScope.backgroundScope,
- )
+ demoImpl = DemoWifiRepository(demoModeWifiDataSource, testScope.backgroundScope)
underTest =
WifiRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 6b371d74eacc..8a4593032748 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.user.data.repository
import android.app.admin.devicePolicyManager
import android.content.pm.UserInfo
+import android.internal.statusbar.fakeStatusBarService
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
@@ -29,6 +30,7 @@ import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectedUserModel
@@ -61,6 +63,7 @@ class UserRepositoryImplTest : SysuiTestCase() {
private val globalSettings = kosmos.fakeGlobalSettings
private val broadcastDispatcher = kosmos.broadcastDispatcher
private val devicePolicyManager = kosmos.devicePolicyManager
+ private val statusBarService = kosmos.fakeStatusBarService
@Mock private lateinit var manager: UserManager
@@ -72,6 +75,10 @@ class UserRepositoryImplTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
tracker = FakeUserTracker()
+ context.orCreateTestableResources.addOverride(
+ R.bool.config_userSwitchingMustGoThroughLoginScreen,
+ false,
+ )
}
@Test
@@ -323,6 +330,8 @@ class UserRepositoryImplTest : SysuiTestCase() {
tracker = tracker,
broadcastDispatcher = broadcastDispatcher,
devicePolicyManager = devicePolicyManager,
+ resources = context.resources,
+ statusBarService = statusBarService,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
index 26439df45ba3..f70b42638cda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
@@ -49,35 +49,78 @@ class UserLogoutInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
userRepository.setUserInfos(USER_INFOS)
- runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[1]) }
+ runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) }
+ userRepository.setLogoutToSystemUserEnabled(false)
+ userRepository.setSecondaryUserLogoutEnabled(false)
}
@Test
- fun logOut_doesNothing_whenAdminDisabledSecondaryLogout() {
+ fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() {
testScope.runTest {
val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
- val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
- userRepository.setSecondaryUserLogoutEnabled(false)
+ val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
assertThat(isLogoutEnabled).isFalse()
underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount)
+ .isEqualTo(secondaryUserLogoutCount)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
+ }
+ }
+
+ @Test
+ fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+ userRepository.setSecondaryUserLogoutEnabled(true)
+ assertThat(isLogoutEnabled).isTrue()
+ underTest.logOut()
+ assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
+ }
+ }
+
+ @Test
+ fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() {
+ testScope.runTest {
+ val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+ val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+ userRepository.setLogoutToSystemUserEnabled(true)
+ assertThat(isLogoutEnabled).isTrue()
+ underTest.logOut()
assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount + 1)
}
}
@Test
- fun logOut_logsOut_whenAdminEnabledSecondaryLogout() {
+ fun logOut_secondaryUserTakesPrecedence() {
testScope.runTest {
val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+ val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+ userRepository.setLogoutToSystemUserEnabled(true)
userRepository.setSecondaryUserLogoutEnabled(true)
assertThat(isLogoutEnabled).isTrue()
underTest.logOut()
assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+ assertThat(userRepository.logOutToSystemUserCallCount)
+ .isEqualTo(logoutToSystemUserCount)
}
}
companion object {
private val USER_INFOS =
- listOf(UserInfo(0, "System user", 0), UserInfo(10, "Regular user", 0))
+ listOf(
+ UserInfo(0, "System user", 0),
+ UserInfo(10, "Regular user", 0),
+ UserInfo(11, "Secondary user", 0),
+ )
}
}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82c8c44f1efe..0854eb46ffdd 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1086,4 +1086,9 @@
enable the desktop specific features.
-->
<bool name="config_enableDesktopFeatureSet">false</bool>
+
+ <!--
+ Whether the user switching can only happen by logging out and going through the system user (login screen).
+ -->
+ <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool>
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 24d619119983..df9f7053c3f3 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -35,6 +35,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.customization.R
import com.android.systemui.dagger.qualifiers.Background
@@ -62,6 +63,7 @@ import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
import com.android.systemui.res.R as SysuiR
+import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -80,7 +82,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -103,6 +104,7 @@ constructor(
private val featureFlags: FeatureFlagsClassic,
private val zenModeController: ZenModeController,
private val zenModeInteractor: ZenModeInteractor,
+ private val userTracker: UserTracker,
) {
var loggers =
listOf(
@@ -120,6 +122,10 @@ constructor(
connectClock(value)
}
+ private fun is24HourFormat(userId: Int? = null): Boolean {
+ return DateFormat.is24HourFormat(context, userId ?: userTracker.userId)
+ }
+
private fun disconnectClock(clock: ClockController?) {
if (clock == null) {
return
@@ -186,7 +192,7 @@ constructor(
var pastVisibility: Int? = null
override fun onViewAttachedToWindow(view: View) {
- clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ clock.events.onTimeFormatChanged(is24HourFormat())
// Match the asing for view.parent's layout classes.
smallClockFrame =
(view.parent as ViewGroup)?.also { frame ->
@@ -218,7 +224,7 @@ constructor(
largeClockOnAttachStateChangeListener =
object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(p0: View) {
- clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ clock.events.onTimeFormatChanged(is24HourFormat())
}
override fun onViewDetachedFromWindow(p0: View) {}
@@ -358,7 +364,7 @@ constructor(
}
override fun onTimeFormatChanged(timeFormat: String?) {
- clock?.run { events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) }
+ clock?.run { events.onTimeFormatChanged(is24HourFormat()) }
}
override fun onTimeZoneChanged(timeZone: TimeZone) {
@@ -366,7 +372,7 @@ constructor(
}
override fun onUserSwitchComplete(userId: Int) {
- clock?.run { events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) }
+ clock?.run { events.onTimeFormatChanged(is24HourFormat(userId)) }
zenModeCallback.onNextAlarmChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index f8254592819b..a107322423bb 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -36,6 +36,7 @@ import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
+import com.android.systemui.qs.panels.domain.backup.QSPreferencesBackupHelper
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManagerImpl
@@ -58,9 +59,9 @@ open class BackupHelper : BackupAgentHelper() {
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
"systemui.keyguard.quickaffordance.shared_preferences"
- private const val COMMUNAL_PREFS_BACKUP_KEY =
- "systemui.communal.shared_preferences"
+ private const val COMMUNAL_PREFS_BACKUP_KEY = "systemui.communal.shared_preferences"
private const val COMMUNAL_STATE_BACKUP_KEY = "systemui.communal_state"
+ private const val QS_PREFERENCES_BACKUP_KEY = "systemui.qs.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
@@ -74,22 +75,20 @@ open class BackupHelper : BackupAgentHelper() {
val keys = PeopleBackupHelper.getFilesToBackup()
addHelper(
PEOPLE_TILES_BACKUP_KEY,
- PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray()),
)
addHelper(
KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
- KeyguardQuickAffordanceBackupHelper(
- context = this,
- userId = userHandle.identifier,
- ),
+ KeyguardQuickAffordanceBackupHelper(context = this, userId = userHandle.identifier),
+ )
+ addHelper(
+ QS_PREFERENCES_BACKUP_KEY,
+ QSPreferencesBackupHelper(context = this, userId = userHandle.identifier),
)
if (communalEnabled()) {
addHelper(
COMMUNAL_PREFS_BACKUP_KEY,
- CommunalPrefsBackupHelper(
- context = this,
- userId = userHandle.identifier,
- )
+ CommunalPrefsBackupHelper(context = this, userId = userHandle.identifier),
)
addHelper(
COMMUNAL_STATE_BACKUP_KEY,
@@ -110,10 +109,7 @@ open class BackupHelper : BackupAgentHelper() {
}
private fun addControlsHelper(userId: Int) {
- val file = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = CONTROLS,
- )
+ val file = UserFileManagerImpl.createFile(userId = userId, fileName = CONTROLS)
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(file.getPath() to getPPControlsFile(this, userId))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
@@ -134,6 +130,7 @@ open class BackupHelper : BackupAgentHelper() {
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ *
* ```
* actions to take
* ```
@@ -141,7 +138,7 @@ open class BackupHelper : BackupAgentHelper() {
private class NoOverwriteFileBackupHelper(
val lock: Any,
val context: Context,
- val fileNamesAndPostProcess: Map<String, () -> Unit>
+ val fileNamesAndPostProcess: Map<String, () -> Unit>,
) : FileBackupHelper(context, *fileNamesAndPostProcess.keys.toTypedArray()) {
override fun restoreEntity(data: BackupDataInputStream) {
@@ -152,11 +149,12 @@ open class BackupHelper : BackupAgentHelper() {
return
}
synchronized(lock) {
- traceSection("File restore: ${data.key}") {
- super.restoreEntity(data)
- }
- Log.d(TAG, "Finishing restore for ${data.key} for user ${context.userId}. " +
- "Starting postProcess.")
+ traceSection("File restore: ${data.key}") { super.restoreEntity(data) }
+ Log.d(
+ TAG,
+ "Finishing restore for ${data.key} for user ${context.userId}. " +
+ "Starting postProcess.",
+ )
traceSection("Postprocess: ${data.key}") {
fileNamesAndPostProcess.get(data.key)?.invoke()
}
@@ -167,7 +165,7 @@ open class BackupHelper : BackupAgentHelper() {
override fun performBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput?,
- newState: ParcelFileDescriptor?
+ newState: ParcelFileDescriptor?,
) {
synchronized(lock) { super.performBackup(oldState, data, newState) }
}
@@ -176,15 +174,13 @@ open class BackupHelper : BackupAgentHelper() {
private fun getPPControlsFile(context: Context, userId: Int): () -> Unit {
return {
- val file = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = BackupHelper.CONTROLS,
- )
+ val file = UserFileManagerImpl.createFile(userId = userId, fileName = BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
- )
+ val dest =
+ UserFileManagerImpl.createFile(
+ userId = userId,
+ fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+ )
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index c464a66ea0c3..6c335e71cfde 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -18,13 +18,18 @@ package com.android.systemui.deviceentry
import com.android.keyguard.EmptyLockIconViewController
import com.android.keyguard.LockIconViewController
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds
@Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class])
@@ -34,6 +39,13 @@ abstract class DeviceEntryModule {
*/
@Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition>
+ @Binds
+ @IntoMap
+ @ClassKey(DeviceUnlockedInteractor.Activator::class)
+ abstract fun deviceUnlockedInteractorActivator(
+ activator: DeviceUnlockedInteractor.Activator
+ ): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 3f937bba46d4..675f00a89d23 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -5,6 +5,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
@@ -42,6 +43,8 @@ interface DeviceEntryRepository {
*/
val isBypassEnabled: StateFlow<Boolean>
+ val deviceUnlockStatus: MutableStateFlow<DeviceUnlockStatus>
+
/**
* Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
* chosen any secure authentication method and even if they set the lockscreen to be dismissed
@@ -84,6 +87,9 @@ constructor(
initialValue = keyguardBypassController.bypassEnabled,
)
+ override val deviceUnlockStatus =
+ MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
val selectedUserId = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5259c5dca39f..24278ecc76bd 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,7 +16,9 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
@@ -26,42 +28,40 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
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.stateIn
+import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceUnlockedInteractor
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
- deviceEntryRepository: DeviceEntryRepository,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val repository: DeviceEntryRepository,
trustInteractor: TrustInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) {
+) : ExclusiveActivatable() {
private val deviceUnlockSource =
merge(
@@ -69,7 +69,7 @@ constructor(
faceAuthInteractor.isAuthenticated
.filter { it }
.map {
- if (deviceEntryRepository.isBypassEnabled.value) {
+ if (repository.isBypassEnabled.value) {
DeviceUnlockSource.FaceWithBypass
} else {
DeviceUnlockSource.FaceWithoutBypass
@@ -163,43 +163,59 @@ constructor(
* proceed.
*/
val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
- authenticationInteractor.authenticationMethod
- .flatMapLatest { authMethod ->
- if (!authMethod.isSecure) {
- flowOf(DeviceUnlockStatus(true, null))
- } else if (authMethod == AuthenticationMethodModel.Sim) {
- // Device is locked if SIM is locked.
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- combine(
- powerInteractor.isAsleep,
- isInLockdown,
- keyguardTransitionInteractor
- .transitionValue(KeyguardState.AOD)
- .map { it == 1f }
- .distinctUntilChanged(),
- ::Triple,
- )
- .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
- val isForceLocked =
- when {
- isAsleep && !isAod -> true
- isInLockdown -> true
- else -> false
- }
- if (isForceLocked) {
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ repository.deviceUnlockStatus.asStateFlow()
+
+ override suspend fun onActivated(): Nothing {
+ authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
+ if (!authMethod.isSecure) {
+ // Device remains unlocked as long as the authentication method is not secure.
+ Log.d(TAG, "remaining unlocked because auth method not secure")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
+ } else if (authMethod == AuthenticationMethodModel.Sim) {
+ // Device remains locked while SIM is locked.
+ Log.d(TAG, "remaining locked because SIM locked")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+ } else {
+ try {
+ Log.d(TAG, "started watching for lock and unlock events")
+ coroutineScope {
+ launch {
+ // Unlock the device when a new unlock source is detected.
+ deviceUnlockSource.collect {
+ Log.d(TAG, "unlocking due to \"$it\"")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it)
}
}
+
+ launch {
+ // Lock events.
+ merge(
+ // Device goes to sleep.
+ powerInteractor.isAsleep
+ .distinctUntilChanged()
+ .filter { it }
+ .map { "asleep" },
+ // Device enters lockdown.
+ isInLockdown
+ .distinctUntilChanged()
+ .filter { it }
+ .map { "lockdown" },
+ )
+ .collect { reason: String ->
+ Log.d(TAG, "locking due to \"$reason\"")
+ repository.deviceUnlockStatus.value =
+ DeviceUnlockStatus(false, null)
+ }
+ }
+ }
+ } finally {
+ Log.d(TAG, "stopped watching for lock and unlock events")
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = DeviceUnlockStatus(false, null),
- )
+ }
+
+ awaitCancellation()
+ }
private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
return when (this) {
@@ -226,7 +242,20 @@ constructor(
return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
}
+ /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */
+ class Activator
+ @Inject
+ constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val interactor: DeviceUnlockedInteractor,
+ ) : CoreStartable {
+ override fun start() {
+ applicationScope.launch { interactor.activate() }
+ }
+ }
+
companion object {
+ private val TAG = "DeviceUnlockedInteractor"
@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/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index c11005d38986..a595d815e016 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -93,18 +93,18 @@ class ClockSizeTransition(
fromBounds: Rect,
toBounds: Rect,
fromSSBounds: Rect?,
- toSSBounds: Rect?
+ toSSBounds: Rect?,
) {}
override fun createAnimator(
sceenRoot: ViewGroup,
startValues: TransitionValues?,
- endValues: TransitionValues?
+ endValues: TransitionValues?,
): Animator? {
if (startValues == null || endValues == null) {
Log.w(
TAG,
- "Couldn't create animator: startValues=$startValues; endValues=$endValues"
+ "Couldn't create animator: startValues=$startValues; endValues=$endValues",
)
return null
}
@@ -137,7 +137,7 @@ class ClockSizeTransition(
"Skipping no-op transition: $toView; " +
"vis: $fromVis -> $toVis; " +
"alpha: $fromAlpha -> $toAlpha; " +
- "bounds: $fromBounds -> $toBounds; "
+ "bounds: $fromBounds -> $toBounds; ",
)
}
return null
@@ -151,7 +151,7 @@ class ClockSizeTransition(
lerp(fromBounds.left, toBounds.left, fract),
lerp(fromBounds.top, toBounds.top, fract),
lerp(fromBounds.right, toBounds.right, fract),
- lerp(fromBounds.bottom, toBounds.bottom, fract)
+ lerp(fromBounds.bottom, toBounds.bottom, fract),
)
fun assignAnimValues(src: String, fract: Float, vis: Int? = null) {
@@ -160,7 +160,7 @@ class ClockSizeTransition(
if (DEBUG) {
Log.i(
TAG,
- "$src: $toView; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;"
+ "$src: $toView; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;",
)
}
toView.setVisibility(vis ?: View.VISIBLE)
@@ -174,7 +174,7 @@ class ClockSizeTransition(
"transitioning: $toView; " +
"vis: $fromVis -> $toVis; " +
"alpha: $fromAlpha -> $toAlpha; " +
- "bounds: $fromBounds -> $toBounds; "
+ "bounds: $fromBounds -> $toBounds; ",
)
}
@@ -258,7 +258,7 @@ class ClockSizeTransition(
fromBounds: Rect,
toBounds: Rect,
fromSSBounds: Rect?,
- toSSBounds: Rect?
+ toSSBounds: Rect?,
) {
// Move normally if clock is not changing visibility
if (fromIsVis == toIsVis) return
@@ -347,12 +347,17 @@ class ClockSizeTransition(
fromBounds: Rect,
toBounds: Rect,
fromSSBounds: Rect?,
- toSSBounds: Rect?
+ toSSBounds: Rect?,
) {
// If view is changing visibility, hold it in place
if (fromIsVis == toIsVis) return
if (DEBUG) Log.i(TAG, "Holding position of ${view.id}")
- toBounds.set(fromBounds)
+
+ if (fromIsVis) {
+ toBounds.set(fromBounds)
+ } else {
+ fromBounds.set(toBounds)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
index d38e507082b9..913aa6f9d547 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -42,7 +42,7 @@ import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.SessionTokenFactory
import com.android.systemui.res.R
-import com.android.systemui.util.Assert
+import com.android.systemui.util.concurrency.Execution
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -63,6 +63,7 @@ constructor(
@Background private val looper: Looper,
@Background private val handler: Handler,
@Background private val bgScope: CoroutineScope,
+ private val execution: Execution,
) {
/**
@@ -108,7 +109,7 @@ constructor(
m3controller: Media3Controller,
token: SessionToken,
): MediaButton? {
- Assert.isNotMainThread()
+ require(!execution.isMainThread())
// First, get standard actions
val playOrPause =
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 f49693a8700b..80ac2fcd4aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -696,7 +696,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
mTaskStackListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
- mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
+ mPipOptional.ifPresent(pip -> pip.removeOnIsInPipStateChangedListener(
+ mOnIsInPipStateChangedListener));
try {
mWindowManagerService.unregisterSystemGestureExclusionListener(
@@ -720,7 +721,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener);
- mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
+ mPipOptional.ifPresent(pip -> pip.addOnIsInPipStateChangedListener(
mOnIsInPipStateChangedListener));
mDesktopModeOptional.ifPresent(
dm -> dm.addDesktopGestureExclusionRegionListener(
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 765b45bdbf2e..bab88c0b0bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -159,7 +159,7 @@ public class QRCodeScannerController implements
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForLockScreenButton() {
- return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
+ return isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
}
/** Returns whether the QR scanner button is allowed on lockscreen. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 624cf306a3b2..e912a0c7faa6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -63,6 +63,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -278,6 +279,24 @@ constructor(
private val mediaSuddenlyAppearingInLandscape: Boolean
get() = !qqsMediaInRow && qsMediaInRow
+ private val collapsedLandscapeMedia by
+ hydrator.hydratedStateOf(
+ traceName = "collapsedLandscapeMedia",
+ initialValue = resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed),
+ source =
+ configurationInteractor.onAnyConfigurationChange.emitOnStart().map {
+ resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed)
+ },
+ )
+
+ private val qqsMediaExpansion: Float
+ get() =
+ if (qqsMediaInRow && collapsedLandscapeMedia) {
+ MediaHostState.COLLAPSED
+ } else {
+ MediaHostState.EXPANDED
+ }
+
private var qsBounds by mutableStateOf(Rect())
private val constrainedSquishinessFraction: Float
@@ -379,6 +398,7 @@ constructor(
initMediaHosts() // init regardless of using media (same as current QS).
coroutineScope {
launch { hydrateSquishinessInteractor() }
+ launch { hydrateQqsMediaExpansion() }
launch { hydrator.activate() }
launch { containerViewModel.activate() }
launch { qqsMediaInRowViewModel.activate() }
@@ -389,7 +409,7 @@ constructor(
private fun initMediaHosts() {
qqsMediaHost.apply {
- expansion = MediaHostState.EXPANDED
+ expansion = qqsMediaExpansion
showsOnlyActiveMedia = true
init(MediaHierarchyManager.LOCATION_QQS)
}
@@ -405,6 +425,10 @@ constructor(
.collect { squishinessInteractor.setSquishinessValue(it) }
}
+ private suspend fun hydrateQqsMediaExpansion() {
+ snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.asIndenting().run {
printSection("Quick Settings state") {
@@ -448,6 +472,8 @@ constructor(
println("qqsMediaInRow", qqsMediaInRow)
println("qsMediaVisible", qsMediaVisible)
println("qsMediaInRow", qsMediaInRow)
+ println("collapsedLandscapeMedia", collapsedLandscapeMedia)
+ println("qqsMediaExpansion", qqsMediaExpansion)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 971598dea0bd..b0c607303297 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -17,9 +17,16 @@
package com.android.systemui.qs.panels.data.repository
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
@@ -29,9 +36,11 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** Repository for QS user preferences. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -43,26 +52,31 @@ constructor(
private val userRepository: UserRepository,
private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @PanelsLog private val logBuffer: LogBuffer,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- /** Whether to show the labels on icon tiles for the current user. */
- val showLabels: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo ->
- val prefs = getSharedPrefs(userInfo.id)
- prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) }
- }
- .flowOn(backgroundDispatcher)
+ private val logger by lazy { Logger(logBuffer, TAG) }
+
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter = IntentFilter(ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+ .onEach { logger.i("Restored state for QS preferences.") }
+ .emitOnStart()
/** Set of [TileSpec] to display as large tiles for the current user. */
val largeTilesSpecs: Flow<Set<TileSpec>> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo ->
+ combine(backupRestorationEvents, userRepository.selectedUserInfo, ::Pair)
+ .flatMapLatest { (_, userInfo) ->
val prefs = getSharedPrefs(userInfo.id)
prefs.observe().emitOnStart().map {
prefs
.getStringSet(
LARGE_TILES_SPECS_KEY,
- defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet()
+ defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet(),
)
?.map { TileSpec.create(it) }
?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
@@ -70,13 +84,6 @@ constructor(
}
.flowOn(backgroundDispatcher)
- /** Sets for the current user whether to show the labels on icon tiles. */
- fun setShowLabels(showLabels: Boolean) {
- with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
- edit().putBoolean(ICON_LABELS_KEY, showLabels).apply()
- }
- }
-
/** Sets for the current user the set of [TileSpec] to display as large tiles. */
fun setLargeTilesSpecs(specs: Set<TileSpec>) {
with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
@@ -85,15 +92,11 @@ constructor(
}
private fun getSharedPrefs(userId: Int): SharedPreferences {
- return userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userId,
- )
+ return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
}
companion object {
- private const val ICON_LABELS_KEY = "show_icon_labels"
+ private const val TAG = "QSPreferencesRepository"
private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
const val FILE_NAME = "quick_settings_prefs"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
new file mode 100644
index 000000000000..2c51ca9c253f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository.Companion.FILE_NAME
+import com.android.systemui.settings.UserFileManagerImpl
+
+class QSPreferencesBackupHelper(context: Context, userId: Int) :
+ SharedPreferencesBackupHelper(
+ context,
+ UserFileManagerImpl.createFile(userId = userId, fileName = FILE_NAME).path,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 42d4effbac3a..63510b873951 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.common.ui.GlobalConfig
@@ -29,12 +30,16 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
/**
* Module responsible for managing display-specific components and resources for the notification
@@ -149,4 +154,25 @@ object ShadeDisplayAwareModule {
configurationInteractor
}
}
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return impl
+ }
+
+ @Provides
+ @IntoMap
+ @ClassKey(ShadePositionRepositoryImpl::class)
+ fun provideShadePositionRepositoryAsCoreStartable(
+ impl: ShadePositionRepositoryImpl
+ ): CoreStartable {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ impl
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
new file mode 100644
index 000000000000..802fc0edb533
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.shade
+
+import android.view.Display
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.statusbar.commandline.Command
+import java.io.PrintWriter
+
+class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) :
+ Command {
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args[0].lowercase() == "reset") {
+ positionRepository.resetDisplayId()
+ pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+ return
+ }
+
+ val displayId: Int =
+ try {
+ args[0].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return
+ }
+
+ if (displayId < 0) {
+ pw.println("Error: display id should be positive integer")
+ }
+
+ positionRepository.setDisplayId(displayId)
+ pw.println("New shade primary display id is $displayId")
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("shade_display_override <displayId> ")
+ pw.println("Set the display which is holding the shade.")
+ pw.println("shade_display_override reset ")
+ pw.println("Reset the display which is holding the shade.")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
new file mode 100644
index 000000000000..24c067ae2371
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.shade.data.repository
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+interface ShadePositionRepository {
+ /** ID of the display which currently hosts the shade */
+ val displayId: StateFlow<Int>
+
+ /**
+ * Updates the value of the shade display id stored, emitting to the new display id to every
+ * component dependent on the shade display id
+ */
+ fun setDisplayId(displayId: Int)
+
+ /** Resets value of shade primary display to the default display */
+ fun resetDisplayId()
+}
+
+/** Source of truth for the display currently holding the shade. */
+@SysUISingleton
+class ShadePositionRepositoryImpl
+@Inject
+constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable {
+ private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+
+ override fun setDisplayId(displayId: Int) {
+ _displayId.value = displayId
+ }
+
+ override fun resetDisplayId() {
+ _displayId.value = Display.DEFAULT_DISPLAY
+ }
+
+ override fun start() {
+ commandRegistry.registerCommand("shade_display_override") {
+ ShadePrimaryDisplayCommand(this)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 3a31851bcb4f..52a79d39bb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.connectivity;
+import static com.android.systemui.Flags.multiuserWifiPickerTrackerSupport;
+
+import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
@@ -65,13 +68,16 @@ public class AccessPointControllerImpl implements AccessPointController,
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
private int mCurrentUser;
+ private Context mContext;
public AccessPointControllerImpl(
+ Context context,
UserManager userManager,
UserTracker userTracker,
Executor mainExecutor,
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
+ mContext = context;
mUserManager = userManager;
mUserTracker = userTracker;
mCurrentUser = userTracker.getUserId();
@@ -87,7 +93,11 @@ public class AccessPointControllerImpl implements AccessPointController,
*/
public void init() {
if (mWifiPickerTracker == null) {
- mWifiPickerTracker = mWifiPickerTrackerFactory.create(this.getLifecycle(), this, TAG);
+ // We are creating the WifiPickerTracker during init to make sure we have one
+ // available at all times however we expect this to be recreated very quickly
+ // with a user-specific context in onUserSwitched.
+ mWifiPickerTracker =
+ mWifiPickerTrackerFactory.create(mContext, this.getLifecycle(), this, TAG);
}
}
@@ -116,6 +126,19 @@ public class AccessPointControllerImpl implements AccessPointController,
void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
+ // Return early if multiuser support is not enabled.
+ if (!multiuserWifiPickerTrackerSupport()) {
+ return;
+ }
+
+ if (mWifiPickerTracker != null) {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ }
+ Context context = mContext.createContextAsUser(UserHandle.of(newUserId), /* flags= */ 0);
+ mWifiPickerTracker = mWifiPickerTrackerFactory.create(context, mLifecycle, this, TAG);
+ if (!mCallbacks.isEmpty()) {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
index dc2ebe55fb73..947ce33274d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
@@ -22,6 +22,7 @@ import android.net.wifi.WifiManager
import android.os.Handler
import android.os.SimpleClock
import androidx.lifecycle.Lifecycle
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.concurrency.ThreadFactory
@@ -41,7 +42,7 @@ import javax.inject.Inject
class WifiPickerTrackerFactory
@Inject
constructor(
- private val context: Context,
+ private val applicationContext: Context,
private val wifiManager: WifiManager?,
private val connectivityManager: ConnectivityManager,
private val systemClock: SystemClock,
@@ -64,16 +65,23 @@ constructor(
* @return a new [WifiPickerTracker] or null if [WifiManager] is null.
*/
fun create(
+ userContext: Context,
lifecycle: Lifecycle,
listener: WifiPickerTrackerCallback,
name: String,
): WifiPickerTracker? {
return if (wifiManager == null) {
null
- } else
+ } else {
+ val contextToUse =
+ if (multiuserWifiPickerTrackerSupport()) {
+ userContext
+ } else {
+ applicationContext
+ }
WifiPickerTracker(
lifecycle,
- context,
+ contextToUse,
wifiManager,
connectivityManager,
mainHandler,
@@ -86,6 +94,7 @@ constructor(
SCAN_INTERVAL_MILLIS,
listener,
)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index f441fd644c17..4c54fc49e536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -112,12 +112,12 @@ constructor(
}
override fun initializeStatusBar() {
- StatusBarSimpleFragment.assertInLegacyMode()
+ StatusBarRootModernization.assertInLegacyMode()
doStart()
}
private fun doStart() {
- if (StatusBarSimpleFragment.isEnabled) doComposeStart() else doLegacyStart()
+ if (StatusBarRootModernization.isEnabled) doComposeStart() else doLegacyStart()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 214151383dc6..057213fa4b18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -21,9 +21,9 @@ import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading and using the status bar simple fragment flag state */
-object StatusBarSimpleFragment {
+object StatusBarRootModernization {
/** Aconfig flag for removing the fragment */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION
/** A token used for dependency declaration */
val token: FlagToken
@@ -32,7 +32,7 @@ object StatusBarSimpleFragment {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarSimpleFragment()
+ get() = Flags.statusBarRootModernization()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index 2fded34a56a0..e2328497d9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
import android.app.NotificationManager
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -50,7 +51,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* If the setting is enabled, this will track seen notifications and ensure that they only show in
@@ -74,7 +74,7 @@ constructor(
private val unseenNotifications = mutableSetOf<NotificationEntry>()
private var isShadeVisible = false
- private var unseenFilterEnabled = false
+ private var minimalismEnabled = false
override fun attach(pipeline: NotifPipeline) {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) {
@@ -83,7 +83,7 @@ constructor(
pipeline.addPromoter(unseenNotifPromoter)
pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
pipeline.addCollectionListener(collectionListener)
- scope.launch { trackUnseenFilterSettingChanges() }
+ scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
dumpManager.registerDumpable(this)
}
@@ -136,12 +136,12 @@ constructor(
return seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled()
}
- private suspend fun trackUnseenFilterSettingChanges() {
+ private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
// Only filter the seen notifs when the lock screen minimalism feature settings is on.
minimalismFeatureSettingEnabled().collectLatest { isMinimalismSettingEnabled ->
// update local field and invalidate if necessary
- if (isMinimalismSettingEnabled != unseenFilterEnabled) {
- unseenFilterEnabled = isMinimalismSettingEnabled
+ if (isMinimalismSettingEnabled != minimalismEnabled) {
+ minimalismEnabled = isMinimalismSettingEnabled
unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
@@ -156,21 +156,21 @@ constructor(
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (unseenFilterEnabled && !isShadeVisible) {
+ if (minimalismEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (unseenFilterEnabled && !isShadeVisible) {
+ if (minimalismEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
+ if (minimalismEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -178,7 +178,7 @@ constructor(
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
- if (!unseenFilterEnabled) return
+ if (!minimalismEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
@@ -215,6 +215,7 @@ constructor(
override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
when {
NotificationMinimalism.isUnexpectedlyInLegacyMode() -> false
+ !minimalismEnabled -> false
seenNotificationsInteractor.isTopOngoingNotification(child) -> true
!NotificationMinimalism.ungroupTopUnseen -> false
else -> seenNotificationsInteractor.isTopUnseenNotification(child)
@@ -225,6 +226,7 @@ constructor(
object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
override fun isInSection(entry: ListEntry): Boolean {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+ if (!minimalismEnabled) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
}
@@ -235,6 +237,7 @@ constructor(
object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
override fun isInSection(entry: ListEntry): Boolean {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+ if (!minimalismEnabled) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 3bc549543ef2..5dff8120f33f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -21,12 +21,14 @@ import android.util.Log
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -38,6 +40,9 @@ import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates.notNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
private const val TAG = "NotifStackSizeCalc"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
@@ -56,7 +61,9 @@ constructor(
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
private val mediaDataManager: MediaDataManager,
@Main private val resources: Resources,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
+ @Application private val scope: CoroutineScope,
) {
/**
@@ -74,7 +81,7 @@ constructor(
/** Whether we allow keyguard to show less important notifications above the shelf. */
private val limitLockScreenToOneImportant
- get() = NotificationMinimalism.isEnabled
+ get() = NotificationMinimalism.isEnabled && minimalismSettingEnabled
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
@@ -85,8 +92,14 @@ constructor(
*/
private var saveSpaceOnLockscreen = false
+ /** True when the lock screen notification minimalism feature setting is enabled */
+ private var minimalismSettingEnabled = false
+
init {
updateResources()
+ if (NotificationMinimalism.isEnabled) {
+ scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
+ }
}
private fun allowedByPolicy(stackHeight: StackHeight): Boolean =
@@ -199,7 +212,7 @@ constructor(
canStackFitInSpace(
heightResult,
notifSpace = notifSpace,
- shelfSpace = shelfSpace
+ shelfSpace = shelfSpace,
) == FitResult.FIT
}
@@ -229,7 +242,7 @@ constructor(
canStackFitInSpace(
heightResult,
notifSpace = notifSpace,
- shelfSpace = shelfSpace
+ shelfSpace = shelfSpace,
) != FitResult.NO_FIT
}
log { "\t--- maxNotifications=$maxNotifications" }
@@ -277,7 +290,7 @@ constructor(
fun computeHeight(
stack: NotificationStackScrollLayout,
maxNotifs: Int,
- shelfHeight: Float
+ shelfHeight: Float,
): Float {
log { "\n" }
log { "computeHeight ---" }
@@ -311,7 +324,7 @@ constructor(
private enum class FitResult {
FIT,
FIT_IF_SAVE_SPACE,
- NO_FIT
+ NO_FIT,
}
data class SpaceNeeded(
@@ -319,7 +332,7 @@ constructor(
val whenEnoughSpace: Float,
// Float height of space needed when showing collapsed layout for FSI HUNs.
- val whenSavingSpace: Float
+ val whenSavingSpace: Float,
)
private data class StackHeight(
@@ -335,9 +348,19 @@ constructor(
val shelfHeightWithSpaceBefore: Float,
/** Whether the stack should actually be forced into the shelf before this height. */
- val shouldForceIntoShelf: Boolean
+ val shouldForceIntoShelf: Boolean,
)
+ private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
+ seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled().collectLatest {
+ if (it != minimalismSettingEnabled) {
+ minimalismSettingEnabled = it
+ }
+ Log.i(TAG, "minimalismSettingEnabled: $minimalismSettingEnabled")
+ }
+ }
+
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
shelfHeight: Float,
@@ -377,7 +400,7 @@ constructor(
stack,
previous = currentNotification,
current = children[firstViewInShelfIndex],
- currentIndex = firstViewInShelfIndex
+ currentIndex = firstViewInShelfIndex,
)
spaceBeforeShelf + shelfHeight
}
@@ -390,14 +413,15 @@ constructor(
log {
"\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
"notifsHeightSavingSpace=$notifsWithCollapsedHun" +
- " shelfWithSpaceBefore=$shelfWithSpaceBefore"
+ " shelfWithSpaceBefore=$shelfWithSpaceBefore" +
+ " limitLockScreenToOneImportant: $limitLockScreenToOneImportant"
}
yield(
StackHeight(
notifsHeight = notifications,
notifsHeightSavingSpace = notifsWithCollapsedHun,
shelfHeightWithSpaceBefore = shelfWithSpaceBefore,
- shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false
+ shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false,
)
)
}
@@ -462,6 +486,10 @@ constructor(
fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
+ pw.println(
+ "NotificationStackSizeCalculator " +
+ "limitLockScreenToOneImportant=$limitLockScreenToOneImportant"
+ )
}
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
@@ -484,7 +512,7 @@ constructor(
stack: NotificationStackScrollLayout,
previous: ExpandableView?,
current: ExpandableView?,
- currentIndex: Int
+ currentIndex: Int,
): Float {
if (currentIndex == 0) {
return 0f
@@ -536,11 +564,7 @@ constructor(
takeWhile(predicate).count() - 1
/** Counts the number of notifications for each type of bucket */
- data class BucketTypeCounter(
- var ongoing: Int = 0,
- var important: Int = 0,
- var other: Int = 0,
- ) {
+ data class BucketTypeCounter(var ongoing: Int = 0, var important: Int = 0, var other: Int = 0) {
fun incrementForBucket(@PriorityBucket bucket: Int?) {
when (bucket) {
BUCKET_MEDIA_CONTROLS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7389086296a3..80c8e8b2a109 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -203,7 +203,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializer;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.data.model.StatusBarMode;
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -1231,7 +1231,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
checkBarModes();
});
}
- if (!StatusBarSimpleFragment.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) {
// When the flag is on, we register the fragment as a core startable and this is not
// needed
mStatusBarInitializer.initializeStatusBar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index ba878edc1132..58386b0cad7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -30,7 +30,7 @@ import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.core.StatusBarInitializerImpl
import com.android.systemui.statusbar.core.StatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarOrchestrator
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
@@ -87,7 +87,7 @@ interface StatusBarPhoneModule {
return if (StatusBarConnectedDisplays.isEnabled) {
// Will be started through MultiDisplayStatusBarStarter
CoreStartable.NOP
- } else if (StatusBarSimpleFragment.isEnabled) {
+ } else if (StatusBarRootModernization.isEnabled) {
defaultInitializerLazy.get()
} else {
// Will be started through CentralSurfaces
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 5cc44762dde8..c55a63c9edf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -58,7 +58,7 @@ import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
@@ -365,7 +365,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary);
mSecondaryOngoingActivityChip =
mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary);
- if (!StatusBarSimpleFragment.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled()) {
showEndSideContent(false);
showClock(false);
}
@@ -464,7 +464,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
super.onPause();
mCommandQueue.removeCallback(this);
mStatusBarStateController.removeCallback(this);
- if (!StatusBarSimpleFragment.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled()) {
mOngoingCallController.removeCallback(mOngoingCallListener);
}
mAnimationScheduler.removeCallback(this);
@@ -507,7 +507,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
}
- if (!StatusBarSimpleFragment.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled()) {
updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
}
Trace.endSection();
@@ -528,7 +528,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
new StatusBarVisibilityChangeListener() {
@Override
public void onStatusBarVisibilityMaybeChanged() {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
updateStatusBarVisibilities(/* animate= */ true);
@@ -536,7 +536,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Override
public void onTransitionFromLockscreenToDreamStarted() {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
mTransitionFromLockscreenToDreamStarted = true;
@@ -547,7 +547,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
boolean hasPrimaryOngoingActivity,
boolean hasSecondaryOngoingActivity,
boolean shouldAnimate) {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity;
@@ -558,7 +558,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Override
public void onIsHomeStatusBarAllowedBySceneChanged(
boolean isHomeStatusBarAllowedByScene) {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene;
@@ -568,7 +568,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
if (displayId != getContext().getDisplayId()) {
@@ -582,7 +582,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void updateStatusBarVisibilities(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
@@ -623,7 +623,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private StatusBarVisibilityModel calculateInternalModel(
StatusBarVisibilityModel externalModel) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
// TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
boolean headsUpVisible =
@@ -677,7 +677,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* mLastModifiedVisibility.
*/
private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
@@ -714,7 +714,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private boolean shouldHideStatusBar() {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
boolean isDefaultDisplay = getContext().getDisplayId() == Display.DEFAULT_DISPLAY;
boolean shouldHideForCurrentDisplay =
@@ -776,7 +776,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void hideEndSideContent(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
if (!animate || !mAnimationsEnabled) {
mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
} else {
@@ -786,7 +786,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void showEndSideContent(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
if (!animate || !mAnimationsEnabled) {
mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
return;
@@ -803,18 +803,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void hideClock(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateHiddenState(mClockView, clockHiddenMode(), animate);
}
private void showClock(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateShow(mClockView, animate);
}
/** Hides the primary ongoing activity chip. */
private void hidePrimaryOngoingActivityChip(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate);
}
@@ -826,18 +826,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* activities. See b/332662551.
*/
private void showPrimaryOngoingActivityChip(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateShow(mPrimaryOngoingActivityChip, animate);
}
private void hideSecondaryOngoingActivityChip(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate);
}
private void showSecondaryOngoingActivityChip(boolean animate) {
StatusBarNotifChips.assertInNewMode();
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateShow(mSecondaryOngoingActivityChip, animate);
}
@@ -846,7 +846,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* don't set the clock GONE otherwise it'll mess up the animation.
*/
private int clockHiddenMode() {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
&& !mStatusBarStateController.isDozing()) {
return View.INVISIBLE;
@@ -855,24 +855,24 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
public void hideNotificationIconArea(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateHide(mNotificationIconAreaInner, animate);
}
public void showNotificationIconArea(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateShow(mNotificationIconAreaInner, animate);
}
public void hideOperatorName(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
if (mOperatorNameViewController != null) {
animateHide(mOperatorNameViewController.getView(), animate);
}
}
public void showOperatorName(boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
if (mOperatorNameViewController != null) {
animateShow(mOperatorNameViewController.getView(), animate);
}
@@ -882,7 +882,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* Animate a view to INVISIBLE or GONE
*/
private void animateHiddenState(final View v, int state, boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
v.animate().cancel();
if (!animate || !mAnimationsEnabled) {
v.setAlpha(0f);
@@ -902,7 +902,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* Hides a view.
*/
private void animateHide(final View v, boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
animateHiddenState(v, View.INVISIBLE, animate);
}
@@ -910,7 +910,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
*/
private void animateShow(View v, boolean animate) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarRootModernization.assertInLegacyMode();
v.animate().cancel();
v.setVisibility(View.VISIBLE);
if (!animate || !mAnimationsEnabled) {
@@ -949,7 +949,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mOperatorNameViewController.init();
// This view should not be visible on lock-screen
if (mKeyguardStateController.isShowing()) {
- if (!StatusBarSimpleFragment.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled()) {
hideOperatorName(false);
}
}
@@ -957,7 +957,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void initOngoingCallChip() {
- if (!StatusBarSimpleFragment.isEnabled()) {
+ if (!StatusBarRootModernization.isEnabled()) {
mOngoingCallController.addCallback(mOngoingCallListener);
}
// TODO(b/364653005): Do we also need to set the secondary activity chip?
@@ -969,7 +969,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Override
public void onDozingChanged(boolean isDozing) {
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
return;
}
updateStatusBarVisibilities(/* animate= */ false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
index eaf15a8cbe17..e4929b574bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -20,7 +20,7 @@ import android.view.View
import androidx.core.animation.Interpolator
import androidx.core.animation.ValueAnimator
import com.android.app.animation.InterpolatorsAndroidX
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
/**
* A controller that keeps track of multiple sources applying alpha value changes to a view. It will
@@ -75,7 +75,7 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) {
private fun applyAlphaToView() {
val minAlpha = getMinAlpha()
- if (!StatusBarSimpleFragment.isEnabled) {
+ if (!StatusBarRootModernization.isEnabled) {
view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
view.alpha = minAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 9cbfc440ab16..94e9d26c9dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -21,6 +21,7 @@ import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import android.telephony.satellite.NtnSignalStrength
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.dagger.SysUISingleton
@@ -31,11 +32,7 @@ import javax.inject.Inject
/** Logs for inputs into the mobile pipeline. */
@SysUISingleton
-class MobileInputLogger
-@Inject
-constructor(
- @MobileInputLog private val buffer: LogBuffer,
-) {
+class MobileInputLogger @Inject constructor(@MobileInputLog private val buffer: LogBuffer) {
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
buffer.log(
TAG,
@@ -49,7 +46,7 @@ constructor(
{
"onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" +
" operator=$str1"
- }
+ },
)
}
@@ -61,7 +58,7 @@ constructor(
int1 = subId
bool1 = serviceState.isEmergencyOnly
},
- { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" }
+ { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" },
)
}
@@ -70,7 +67,7 @@ constructor(
TAG,
LogLevel.INFO,
{ int1 = subId },
- { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" }
+ { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" },
)
}
@@ -82,7 +79,16 @@ constructor(
int1 = subId
str1 = signalStrength.toString()
},
- { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" }
+ { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" },
+ )
+ }
+
+ fun logNtnSignalStrengthChanged(signalStrength: NtnSignalStrength) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = signalStrength.level },
+ { "onCarrierRoamingNtnSignalStrengthChanged: level=$int1" },
)
}
@@ -128,7 +134,7 @@ constructor(
TAG,
LogLevel.INFO,
{ bool1 = active },
- { "onCarrierRoamingNtnModeChanged: $bool1" }
+ { "onCarrierRoamingNtnModeChanged: $bool1" },
)
}
@@ -146,12 +152,7 @@ constructor(
}
fun logCarrierConfigChanged(subId: Int) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { int1 = subId },
- { "onCarrierConfigChanged: subId=$int1" },
- )
+ buffer.log(TAG, LogLevel.INFO, { int1 = subId }, { "onCarrierConfigChanged: subId=$int1" })
}
fun logOnDataEnabledChanged(enabled: Boolean, subId: Int) {
@@ -175,7 +176,7 @@ constructor(
TAG,
LogLevel.INFO,
{ str1 = config.toString() },
- { "defaultDataSubRatConfig: $str1" }
+ { "defaultDataSubRatConfig: $str1" },
)
}
@@ -184,7 +185,7 @@ constructor(
TAG,
LogLevel.INFO,
{ str1 = mapping.toString() },
- { "defaultMobileIconMapping: $str1" }
+ { "defaultMobileIconMapping: $str1" },
)
}
@@ -216,7 +217,7 @@ constructor(
{
"Intent: ACTION_SERVICE_PROVIDERS_UPDATED." +
" showSpn=$bool1 spn=$str1 dataSpn=$str2 showPlmn=$bool2 plmn=$str3"
- }
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 205205eac210..07843f1ef041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -107,6 +107,12 @@ interface MobileConnectionRepository {
// @IntRange(from = 0, to = 4)
val primaryLevel: StateFlow<Int>
+ /**
+ * This level can be used to reflect the signal strength when in carrier roaming NTN mode
+ * (carrier-based satellite)
+ */
+ val satelliteLevel: StateFlow<Int>
+
/** The current data connection state. See [DataConnectionState] */
val dataConnectionState: StateFlow<DataConnectionState>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 3261b71ece3c..be3977ecd4ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -37,12 +37,14 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullM
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_SATELLITE_LEVEL
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -75,7 +77,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = "inflate",
- _inflateSignalStrength.value
+ _inflateSignalStrength.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _inflateSignalStrength.value)
@@ -89,7 +91,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_EMERGENCY,
- _isEmergencyOnly.value
+ _isEmergencyOnly.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
@@ -100,7 +102,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_ROAMING,
- _isRoaming.value
+ _isRoaming.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
@@ -111,7 +113,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_OPERATOR,
- _operatorAlphaShort.value
+ _operatorAlphaShort.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
@@ -122,7 +124,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_IN_SERVICE,
- _isInService.value
+ _isInService.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
@@ -133,7 +135,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_NTN,
- _isNonTerrestrial.value
+ _isNonTerrestrial.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isNonTerrestrial.value)
@@ -144,7 +146,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_GSM,
- _isGsm.value
+ _isGsm.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
@@ -155,7 +157,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_CDMA_LEVEL,
- _cdmaLevel.value
+ _cdmaLevel.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
@@ -166,10 +168,21 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_PRIMARY_LEVEL,
- _primaryLevel.value
+ _primaryLevel.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
+ private val _satelliteLevel = MutableStateFlow(0)
+ override val satelliteLevel: StateFlow<Int> =
+ _satelliteLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_SATELLITE_LEVEL,
+ _satelliteLevel.value,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _satelliteLevel.value)
+
private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
override val dataConnectionState =
_dataConnectionState
@@ -177,12 +190,7 @@ class DemoMobileConnectionRepository(
.stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
private val _dataActivityDirection =
- MutableStateFlow(
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- )
- )
+ MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
override val dataActivityDirection =
_dataActivityDirection
.logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
@@ -195,7 +203,7 @@ class DemoMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_CARRIER_NETWORK_CHANGE,
- _carrierNetworkChangeActive.value
+ _carrierNetworkChangeActive.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 2e4767893c3d..75f613d7e3c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -90,7 +90,7 @@ class CarrierMergedConnectionRepository(
TAG,
"Connection repo subId=$subId " +
"does not equal wifi repo subId=${network.subscriptionId}; " +
- "not showing carrier merged"
+ "not showing carrier merged",
)
null
}
@@ -149,7 +149,7 @@ class CarrierMergedConnectionRepository(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- ResolvedNetworkType.UnknownNetworkType
+ ResolvedNetworkType.UnknownNetworkType,
)
override val dataConnectionState =
@@ -173,6 +173,7 @@ class CarrierMergedConnectionRepository(
override val isNonTerrestrial = MutableStateFlow(false).asStateFlow()
override val isGsm = MutableStateFlow(false).asStateFlow()
override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+ override val satelliteLevel = MutableStateFlow(0)
/**
* Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
@@ -207,10 +208,7 @@ class CarrierMergedConnectionRepository(
@Application private val scope: CoroutineScope,
private val wifiRepository: WifiRepository,
) {
- fun build(
- subId: Int,
- mobileLogger: TableLogBuffer,
- ): MobileConnectionRepository {
+ fun build(subId: Int, mobileLogger: TableLogBuffer): MobileConnectionRepository {
return CarrierMergedConnectionRepository(
subId,
mobileLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a5e47a6e68cd..fae9be083e12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -132,12 +132,12 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_EMERGENCY,
- activeRepo.value.isEmergencyOnly.value
+ activeRepo.value.isEmergencyOnly.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.isEmergencyOnly.value
+ activeRepo.value.isEmergencyOnly.value,
)
override val isRoaming =
@@ -147,7 +147,7 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_ROAMING,
- activeRepo.value.isRoaming.value
+ activeRepo.value.isRoaming.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
@@ -158,12 +158,12 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_OPERATOR,
- activeRepo.value.operatorAlphaShort.value
+ activeRepo.value.operatorAlphaShort.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.operatorAlphaShort.value
+ activeRepo.value.operatorAlphaShort.value,
)
override val isInService =
@@ -173,7 +173,7 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_IN_SERVICE,
- activeRepo.value.isInService.value
+ activeRepo.value.isInService.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
@@ -184,12 +184,12 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_NTN,
- activeRepo.value.isNonTerrestrial.value
+ activeRepo.value.isNonTerrestrial.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.isNonTerrestrial.value
+ activeRepo.value.isNonTerrestrial.value,
)
override val isGsm =
@@ -199,7 +199,7 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_IS_GSM,
- activeRepo.value.isGsm.value
+ activeRepo.value.isGsm.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
@@ -210,7 +210,7 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_CDMA_LEVEL,
- activeRepo.value.cdmaLevel.value
+ activeRepo.value.cdmaLevel.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
@@ -221,22 +221,33 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_PRIMARY_LEVEL,
- activeRepo.value.primaryLevel.value
+ activeRepo.value.primaryLevel.value,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
+ override val satelliteLevel: StateFlow<Int> =
+ activeRepo
+ .flatMapLatest { it.satelliteLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_SATELLITE_LEVEL,
+ activeRepo.value.satelliteLevel.value,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.satelliteLevel.value)
+
override val dataConnectionState =
activeRepo
.flatMapLatest { it.dataConnectionState }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- activeRepo.value.dataConnectionState.value
+ activeRepo.value.dataConnectionState.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.dataConnectionState.value
+ activeRepo.value.dataConnectionState.value,
)
override val dataActivityDirection =
@@ -245,12 +256,12 @@ class FullMobileConnectionRepository(
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- activeRepo.value.dataActivityDirection.value
+ activeRepo.value.dataActivityDirection.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.dataActivityDirection.value
+ activeRepo.value.dataActivityDirection.value,
)
override val carrierNetworkChangeActive =
@@ -260,12 +271,12 @@ class FullMobileConnectionRepository(
tableLogBuffer,
columnPrefix = "",
columnName = COL_CARRIER_NETWORK_CHANGE,
- activeRepo.value.carrierNetworkChangeActive.value
+ activeRepo.value.carrierNetworkChangeActive.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.carrierNetworkChangeActive.value
+ activeRepo.value.carrierNetworkChangeActive.value,
)
override val resolvedNetworkType =
@@ -274,12 +285,12 @@ class FullMobileConnectionRepository(
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- activeRepo.value.resolvedNetworkType.value
+ activeRepo.value.resolvedNetworkType.value,
)
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.resolvedNetworkType.value
+ activeRepo.value.resolvedNetworkType.value,
)
override val dataEnabled =
@@ -305,7 +316,7 @@ class FullMobileConnectionRepository(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.inflateSignalStrength.value
+ activeRepo.value.inflateSignalStrength.value,
)
override val allowNetworkSliceIndicator =
@@ -320,7 +331,7 @@ class FullMobileConnectionRepository(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- activeRepo.value.allowNetworkSliceIndicator.value
+ activeRepo.value.allowNetworkSliceIndicator.value,
)
override val numberOfLevels =
@@ -439,6 +450,7 @@ class FullMobileConnectionRepository(
const val COL_IS_IN_SERVICE = "isInService"
const val COL_OPERATOR = "operatorName"
const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_SATELLITE_LEVEL = "satelliteLevel"
const val COL_ROAMING = "roaming"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 62bd8ad4317c..8a1e7f9a0096 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -41,6 +41,7 @@ import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import android.telephony.satellite.NtnSignalStrength
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -173,7 +174,7 @@ class MobileConnectionRepositoryImpl(
override fun onDataConnectionStateChanged(
dataState: Int,
- networkType: Int
+ networkType: Int,
) {
logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
trySend(CallbackEvent.OnDataConnectionStateChanged(dataState))
@@ -195,6 +196,17 @@ class MobileConnectionRepositoryImpl(
logger.logOnSignalStrengthsChanged(signalStrength, subId)
trySend(CallbackEvent.OnSignalStrengthChanged(signalStrength))
}
+
+ override fun onCarrierRoamingNtnSignalStrengthChanged(
+ signalStrength: NtnSignalStrength
+ ) {
+ logger.logNtnSignalStrengthChanged(signalStrength)
+ trySend(
+ CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged(
+ signalStrength
+ )
+ )
+ }
}
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
@@ -267,6 +279,12 @@ class MobileConnectionRepositoryImpl(
.map { it.signalStrength.level }
.stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val satelliteLevel: StateFlow<Int> =
+ callbackEvents
+ .mapNotNull { it.onCarrierRoamingNtnSignalStrengthChanged }
+ .map { it.signalStrength.level }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
override val dataConnectionState =
callbackEvents
.mapNotNull { it.onDataConnectionStateChanged }
@@ -280,7 +298,7 @@ class MobileConnectionRepositoryImpl(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false),
)
override val carrierNetworkChangeActive =
@@ -385,7 +403,7 @@ class MobileConnectionRepositoryImpl(
if (
intent.getIntExtra(
EXTRA_SUBSCRIPTION_INDEX,
- INVALID_SUBSCRIPTION_ID
+ INVALID_SUBSCRIPTION_ID,
) == subId
) {
logger.logServiceProvidersUpdatedBroadcast(intent)
@@ -399,7 +417,7 @@ class MobileConnectionRepositoryImpl(
context.registerReceiver(
receiver,
- IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)
+ IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
)
awaitClose { context.unregisterReceiver(receiver) }
@@ -524,6 +542,9 @@ sealed interface CallbackEvent {
data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
+
+ data class OnCarrierRoamingNtnSignalStrengthChanged(val signalStrength: NtnSignalStrength) :
+ CallbackEvent
}
/**
@@ -539,6 +560,9 @@ data class TelephonyCallbackState(
val onDisplayInfoChanged: CallbackEvent.OnDisplayInfoChanged? = null,
val onServiceStateChanged: CallbackEvent.OnServiceStateChanged? = null,
val onSignalStrengthChanged: CallbackEvent.OnSignalStrengthChanged? = null,
+ val onCarrierRoamingNtnSignalStrengthChanged:
+ CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged? =
+ null,
) {
fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
return when (event) {
@@ -555,6 +579,8 @@ data class TelephonyCallbackState(
copy(onServiceStateChanged = event)
}
is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
+ is CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged ->
+ copy(onCarrierRoamingNtnSignalStrengthChanged = event)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4ef328cf1623..1bf14af7ea6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -335,7 +335,11 @@ class MobileIconInteractorImpl(
// Satellite level is unaffected by the inflateSignalStrength property
// See b/346904529 for details
private val satelliteShownLevel: StateFlow<Int> =
- combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ if (Flags.carrierRoamingNbIotNtn()) {
+ connectionRepository.satelliteLevel
+ } else {
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
private val cellularIcon: Flow<SignalIconModel.Cellular> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 11d73397ca22..2177e025b976 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -22,6 +22,7 @@ import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -30,13 +31,12 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel].
@@ -91,7 +91,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
- if (StatusBarSimpleFragment.isEnabled) {
+ if (StatusBarRootModernization.isEnabled) {
when (primaryChipModel) {
is OngoingActivityChipModel.Shown ->
primaryChipView.show(shouldAnimateChange = true)
@@ -133,7 +133,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
// enough space for it.
OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
- if (StatusBarSimpleFragment.isEnabled) {
+ if (StatusBarRootModernization.isEnabled) {
primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
secondaryChipView.adjustVisibility(
chips.secondary.toVisibilityModel()
@@ -160,7 +160,7 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde
}
}
- if (StatusBarSimpleFragment.isEnabled) {
+ if (StatusBarRootModernization.isEnabled) {
val clockView = view.requireViewById<View>(R.id.clock)
launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 247abc3da175..a9c2f72f7b18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -104,7 +104,7 @@ fun StatusBarRoot(
darkIconDispatcher: DarkIconDispatcher,
onViewCreated: (ViewGroup) -> Unit,
) {
- // None of these methods are used when [StatusBarSimpleFragment] is on.
+ // None of these methods are used when [StatusBarRootModernization] is on.
// This can be deleted once the fragment is gone
val nopVisibilityChangeListener =
object : StatusBarVisibilityChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 76024cd565d1..c7b6be3fc4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -17,12 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.annotation.SuppressLint
+import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
+import android.os.UserHandle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -43,6 +46,7 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiReposito
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Unavailable.toHotspotDeviceType
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.user.data.repository.UserRepository
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -53,10 +57,12 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -68,6 +74,8 @@ import kotlinx.coroutines.flow.stateIn
class WifiRepositoryImpl
@Inject
constructor(
+ @Application applicationContext: Context,
+ private val userRepository: UserRepository,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -84,90 +92,226 @@ constructor(
private var wifiPickerTracker: WifiPickerTracker? = null
- private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
- var current =
- WifiPickerTrackerInfo(
- state = WIFI_STATE_DEFAULT,
- isDefault = false,
- primaryNetwork = WIFI_NETWORK_DEFAULT,
- secondaryNetworks = emptyList(),
- )
- callbackFlow {
- val callback =
- object : WifiPickerTracker.WifiPickerTrackerCallback {
- override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
- logOnWifiEntriesChanged(connectedEntry)
-
- val activeNetworks = wifiPickerTracker?.activeWifiEntries ?: emptyList()
- val secondaryNetworks =
- activeNetworks
- .filter { it != connectedEntry && !it.isPrimaryNetwork }
- .map { it.toWifiNetworkModel() }
-
- // [WifiPickerTracker.connectedWifiEntry] will return the same instance
- // but with updated internals. For example, when its validation status
- // changes from false to true, the same instance is re-used but with the
- // validated field updated.
- //
- // Because it's the same instance, the flow won't re-emit the value
- // (even though the internals have changed). So, we need to transform it
- // into our internal model immediately. [toWifiNetworkModel] always
- // returns a new instance, so the flow is guaranteed to emit.
- send(
- newPrimaryNetwork =
- connectedEntry?.toPrimaryWifiNetworkModel()
- ?: WIFI_NETWORK_DEFAULT,
- newSecondaryNetworks = secondaryNetworks,
- newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
- )
- }
+ @VisibleForTesting
+ val selectedUserContext: Flow<Context> =
+ userRepository.selectedUserInfo.map {
+ applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
+ }
- override fun onWifiStateChanged() {
- val state = wifiPickerTracker?.wifiState
- logOnWifiStateChanged(state)
- send(newState = state ?: WIFI_STATE_DEFAULT)
- }
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
- override fun onNumSavedNetworksChanged() {}
-
- override fun onNumSavedSubscriptionsChanged() {}
-
- private fun send(
- newState: Int = current.state,
- newIsDefault: Boolean = current.isDefault,
- newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
- newSecondaryNetworks: List<WifiNetworkModel> =
- current.secondaryNetworks,
- ) {
- val new =
- WifiPickerTrackerInfo(
- newState,
- newIsDefault,
- newPrimaryNetwork,
- newSecondaryNetworks,
- )
- current = new
- trySend(new)
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> =
+ if (multiuserWifiPickerTrackerSupport()) {
+ selectedUserContext
+ .flatMapLatest { currentContext
+ -> // flatMapLatest because when selectedUserContext emits a new value, we want
+ // to
+ // re-create a whole flow
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry =
+ wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ val secondaryNetworks =
+ activeNetworks
+ .filter {
+ it != connectedEntry && !it.isPrimaryNetwork
+ }
+ .map { it.toWifiNetworkModel() }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the
+ // same
+ // instance but with updated internals. For example, when
+ // its
+ // validation status changes from false to true, the same
+ // instance is re-used but with the validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit
+ // the
+ // value (even though the internals have changed). So, we
+ // need
+ // to transform it into our internal model immediately.
+ // [toWifiNetworkModel] always returns a new instance, so
+ // the
+ // flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork =
+ connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel =
+ current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+ wifiPickerTracker =
+ wifiPickerTrackerFactory
+ .create(currentContext, lifecycle, callback, "WifiRepository")
+ .apply {
+ // By default, [WifiPickerTracker] will scan to see all
+ // available wifi networks in the area. Because SysUI only
+ // needs to display the **connected** network, we don't
+ // need scans to be running (and in fact, running scans is
+ // costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
+
+ // The lifecycle must be STARTED in order for the callback to receive
+ // events.
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.STARTED
+ }
+ awaitClose {
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.CREATED
+ }
+ }
}
- }
+ .stateIn(scope, SharingStarted.Eagerly, current)
+ }
+ .stateIn(scope, SharingStarted.Eagerly, current)
+ } else {
- wifiPickerTracker =
- wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
- // By default, [WifiPickerTracker] will scan to see all available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and in fact,
- // running scans is costly and should be avoided whenever possible).
- this?.disableScanning()
+ run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ val secondaryNetworks =
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same
+ // instance
+ // but with updated internals. For example, when its validation
+ // status
+ // changes from false to true, the same instance is re-used but
+ // with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the
+ // value
+ // (even though the internals have changed). So, we need to
+ // transform it
+ // into our internal model immediately. [toWifiNetworkModel]
+ // always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork =
+ connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+
+ wifiPickerTracker =
+ wifiPickerTrackerFactory
+ .create(applicationContext, lifecycle, callback, "WifiRepository")
+ .apply {
+ // By default, [WifiPickerTracker] will scan to see all
+ // available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and
+ // in fact,
+ // running scans is costly and should be avoided whenever
+ // possible).
+ this?.disableScanning()
+ }
+ // The lifecycle must be STARTED in order for the callback to receive
+ // events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.CREATED
+ }
+ }
}
- // The lifecycle must be STARTED in order for the callback to receive events.
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
- awaitClose {
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
- }
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
- .stateIn(scope, SharingStarted.Eagerly, current)
- }
+ }
override val isWifiEnabled: StateFlow<Boolean> =
wifiPickerTrackerInfo
@@ -185,11 +329,7 @@ constructor(
wifiPickerTrackerInfo
.map { it.primaryNetwork }
.distinctUntilChanged()
- .logDiffsForTable(
- tableLogger,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
+ .logDiffsForTable(tableLogger, columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT)
.stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
@@ -348,7 +488,7 @@ constructor(
TAG,
LogLevel.DEBUG,
{ str1 = prettyPrintActivity(activity) },
- { "onActivityChanged: $str1" }
+ { "onActivityChanged: $str1" },
)
}
@@ -379,13 +519,15 @@ constructor(
/** The currently primary wifi network. */
val primaryNetwork: WifiNetworkModel,
/** The current secondary network(s), if any. Specifically excludes the primary network. */
- val secondaryNetworks: List<WifiNetworkModel>
+ val secondaryNetworks: List<WifiNetworkModel>,
)
@SysUISingleton
class Factory
@Inject
constructor(
+ @Application private val applicationContext: Context,
+ private val userRepository: UserRepository,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -395,6 +537,8 @@ constructor(
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
+ applicationContext,
+ userRepository,
scope,
mainExecutor,
bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index c7bd5a1bb9a8..9187e3c43380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -206,12 +206,14 @@ public interface StatusBarPolicyModule {
@SysUISingleton
@Provides
static AccessPointControllerImpl provideAccessPointControllerImpl(
+ @Application Context context,
UserManager userManager,
UserTracker userTracker,
@Main Executor mainExecutor,
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
AccessPointControllerImpl controller = new AccessPointControllerImpl(
+ context,
userManager,
userTracker,
mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index 3f6ef16e2e5e..b79d39e65682 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -35,7 +35,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.compose.ComposeInitializer;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
/**
@@ -64,7 +64,7 @@ public class StatusBarWindowView extends FrameLayout {
public void onAttachedToWindow() {
super.onAttachedToWindow();
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
ComposeInitializer.INSTANCE.onAttachedToWindow(this);
}
}
@@ -73,7 +73,7 @@ public class StatusBarWindowView extends FrameLayout {
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (StatusBarSimpleFragment.isEnabled()) {
+ if (StatusBarRootModernization.isEnabled()) {
ComposeInitializer.INSTANCE.onDetachedFromWindow(this);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index f20ce63467f7..ad97b21ea60b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -23,11 +23,13 @@ import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.content.res.Resources
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -109,6 +111,9 @@ interface UserRepository {
/** Whether logout for secondary users is enabled by admin device policy. */
val isSecondaryUserLogoutEnabled: StateFlow<Boolean>
+ /** Whether logout into system user is enabled. */
+ val isLogoutToSystemUserEnabled: StateFlow<Boolean>
+
/** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
fun refreshUsers()
@@ -121,6 +126,9 @@ interface UserRepository {
/** Performs logout logout for secondary users. */
suspend fun logOutSecondaryUser()
+ /** Performs logout into the system user. */
+ suspend fun logOutToSystemUser()
+
/**
* Returns the user ID of the "main user" of the device. This user may have access to certain
* features which are limited to at most one user. There will never be more than one main user
@@ -143,6 +151,7 @@ class UserRepositoryImpl
@Inject
constructor(
@Application private val appContext: Context,
+ @Main private val resources: Resources,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -151,6 +160,7 @@ constructor(
private val tracker: UserTracker,
private val devicePolicyManager: DevicePolicyManager,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val statusBarService: IStatusBarService,
) : UserRepository {
private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
@@ -275,12 +285,34 @@ constructor(
.stateIn(applicationScope, SharingStarted.Eagerly, false)
@SuppressLint("MissingPermission")
+ override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+ selectedUser
+ .flatMapLatestConflated { selectedUser ->
+ if (selectedUser.isEligibleForLogout()) {
+ flowOf(
+ resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+ )
+ } else {
+ flowOf(false)
+ }
+ }
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+ @SuppressLint("MissingPermission")
override suspend fun logOutSecondaryUser() {
if (isSecondaryUserLogoutEnabled.value) {
withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() }
}
}
+ override suspend fun logOutToSystemUser() {
+ // TODO(b/377493351) : start using proper logout API once it is available.
+ // Using reboot is a temporary solution.
+ if (isLogoutToSystemUserEnabled.value) {
+ withContext(backgroundDispatcher) { statusBarService.reboot(false) }
+ }
+ }
+
@SuppressLint("MissingPermission")
override fun refreshUsers() {
applicationScope.launch {
@@ -316,42 +348,53 @@ constructor(
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
- val isSimpleUserSwitcher =
- globalSettings.getInt(
- SETTING_SIMPLE_USER_SWITCHER,
- if (
- appContext.resources.getBoolean(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher
- )
- ) {
- 1
- } else {
- 0
- },
- ) != 0
-
- val isAddUsersFromLockscreen =
- globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
-
- val isUserSwitcherEnabled =
- globalSettings.getInt(
- Settings.Global.USER_SWITCHER_ENABLED,
- if (
- appContext.resources.getBoolean(
- com.android.internal.R.bool.config_showUserSwitcherByDefault
- )
- ) {
- 1
- } else {
- 0
- },
- ) != 0
-
- UserSwitcherSettingsModel(
- isSimpleUserSwitcher = isSimpleUserSwitcher,
- isAddUsersFromLockscreen = isAddUsersFromLockscreen,
- isUserSwitcherEnabled = isUserSwitcherEnabled,
- )
+ if (
+ // TODO(b/378068979): remove once login screen-specific logic
+ // is implemented at framework level.
+ appContext.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+ ) {
+ UserSwitcherSettingsModel(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = false,
+ isUserSwitcherEnabled = false,
+ )
+ } else {
+ val isSimpleUserSwitcher =
+ globalSettings.getInt(
+ SETTING_SIMPLE_USER_SWITCHER,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
+ ) != 0
+
+ val isAddUsersFromLockscreen =
+ globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
+
+ val isUserSwitcherEnabled =
+ globalSettings.getInt(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_showUserSwitcherByDefault
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
+ ) != 0
+ UserSwitcherSettingsModel(
+ isSimpleUserSwitcher = isSimpleUserSwitcher,
+ isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+ isUserSwitcherEnabled = isUserSwitcherEnabled,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index a7983605eac9..bcbd679b35eb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,6 +21,7 @@ import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.UserManager
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -40,7 +41,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
interface UserSwitcherRepository {
@@ -67,6 +67,9 @@ constructor(
private val showUserSwitcherForSingleUser =
context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
+ private val userSwitchingMustGoThroughLoginScreen =
+ context.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+
override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
suspend fun updateState() {
trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
@@ -135,7 +138,13 @@ constructor(
private suspend fun isUserSwitcherEnabled(): Boolean {
return withContext(bgDispatcher) {
- userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+ // TODO(b/378068979): remove once login screen-specific logic
+ // is implemented at framework level.
+ if (userSwitchingMustGoThroughLoginScreen) {
+ false
+ } else {
+ userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
index 154f1dc3e747..f2dd25fecf08 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
@@ -23,7 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.user.data.repository.UserRepository
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
/** Encapsulates business logic to for the logout. */
@SysUISingleton
@@ -33,11 +36,22 @@ constructor(
private val userRepository: UserRepository,
@Application private val applicationScope: CoroutineScope,
) {
- val isLogoutEnabled: StateFlow<Boolean> = userRepository.isSecondaryUserLogoutEnabled
+
+ val isLogoutEnabled: StateFlow<Boolean> =
+ combine(
+ userRepository.isSecondaryUserLogoutEnabled,
+ userRepository.isLogoutToSystemUserEnabled,
+ Boolean::or,
+ )
+ .stateIn(applicationScope, SharingStarted.Eagerly, false)
fun logOut() {
- if (userRepository.isSecondaryUserLogoutEnabled.value) {
- applicationScope.launch { userRepository.logOutSecondaryUser() }
+ applicationScope.launch {
+ if (userRepository.isSecondaryUserLogoutEnabled.value) {
+ userRepository.logOutSecondaryUser()
+ } else if (userRepository.isLogoutToSystemUserEnabled.value) {
+ userRepository.logOutToSystemUser()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index 9440a9364b62..fb157958a630 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.dialog.dagger
+import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
@@ -28,7 +29,7 @@ import kotlinx.coroutines.CoroutineScope
* [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
*/
@VolumeDialogScope
-@Subcomponent(modules = [])
+@Subcomponent(modules = [VolumeDialogModule::class])
interface VolumeDialogComponent {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
new file mode 100644
index 000000000000..025e269b70b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.dagger.module
+
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module for volume dialog code in the volume package */
+@Module
+interface VolumeDialogModule {
+
+ @Binds
+ fun bindVolumeDialogRingerFeedbackRepository(
+ ringerFeedbackRepository: VolumeDialogRingerFeedbackRepositoryImpl
+ ): VolumeDialogRingerFeedbackRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt
new file mode 100644
index 000000000000..263972b937d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.data.repository
+
+import android.content.Context
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface VolumeDialogRingerFeedbackRepository {
+
+ /** gets number of shown toasts */
+ suspend fun getToastCount(): Int
+
+ /** updates number of shown toasts */
+ suspend fun updateToastCount(toastCount: Int)
+}
+
+class VolumeDialogRingerFeedbackRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Background val backgroundDispatcher: CoroutineDispatcher,
+) : VolumeDialogRingerFeedbackRepository {
+
+ override suspend fun getToastCount(): Int =
+ withContext(backgroundDispatcher) {
+ return@withContext Prefs.getInt(
+ applicationContext,
+ Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
+ 0,
+ )
+ }
+
+ override suspend fun updateToastCount(toastCount: Int) {
+ withContext(backgroundDispatcher) {
+ Prefs.putInt(applicationContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, toastCount + 1)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
index 281e57fec855..b83613ba4f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -26,6 +26,7 @@ import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.plugins.VolumeDialogController
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import javax.inject.Inject
@@ -45,6 +46,7 @@ constructor(
volumeDialogStateInteractor: VolumeDialogStateInteractor,
private val controller: VolumeDialogController,
private val audioSystemRepository: AudioSystemRepository,
+ private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository,
) {
val ringerModel: Flow<VolumeDialogRingerModel> =
@@ -84,4 +86,12 @@ constructor(
fun scheduleTouchFeedback() {
controller.scheduleTouchFeedback()
}
+
+ suspend fun getToastCount(): Int {
+ return ringerFeedbackRepository.getToastCount()
+ }
+
+ suspend fun updateToastCount(toastCount: Int) {
+ ringerFeedbackRepository.updateToastCount(toastCount)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index d4da226152f3..e040638324ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -16,17 +16,23 @@
package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+import android.content.Context
import android.media.AudioAttributes
import android.media.AudioManager.RINGER_MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_SILENT
import android.media.AudioManager.RINGER_MODE_VIBRATE
import android.os.VibrationEffect
+import android.widget.Toast
+import com.android.internal.R as internalR
+import com.android.settingslib.Utils
import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
@@ -40,26 +46,37 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+private const val SHOW_RINGER_TOAST_COUNT = 12
class VolumeDialogRingerDrawerViewModel
@AssistedInject
constructor(
+ @Application private val applicationContext: Context,
@VolumeDialog private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val interactor: VolumeDialogRingerInteractor,
private val vibrator: VibratorHelper,
private val volumeDialogLogger: VolumeDialogLogger,
+ private val visibilityInteractor: VolumeDialogVisibilityInteractor,
) {
private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
val ringerViewModel: StateFlow<RingerViewModelState> =
combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+ level = ringerModel.level
+ levelMax = ringerModel.levelMax
ringerModel.toViewModel(state)
}
.flowOn(backgroundDispatcher)
.stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable)
+ // Level and Maximum level of Ring Stream.
+ private var level = -1
+ private var levelMax = -1
+
// Vibration attributes.
private val sonificiationVibrationAttributes =
AudioAttributes.Builder()
@@ -71,8 +88,10 @@ constructor(
if (drawerState.value is RingerDrawerState.Open) {
Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
provideTouchFeedback(ringerMode)
+ maybeShowToast(ringerMode)
interactor.setRingerMode(ringerMode)
}
+ visibilityInteractor.resetDismissTimeout()
drawerState.value =
when (drawerState.value) {
is RingerDrawerState.Initial -> {
@@ -201,6 +220,46 @@ constructor(
}
}
+ private fun maybeShowToast(ringerMode: RingerMode) {
+ coroutineScope.launch {
+ val seenToastCount = interactor.getToastCount()
+ if (seenToastCount > SHOW_RINGER_TOAST_COUNT) {
+ return@launch
+ }
+
+ val toastText =
+ when (ringerMode.value) {
+ RINGER_MODE_NORMAL -> {
+ if (level != -1 && levelMax != -1) {
+ applicationContext.getString(
+ R.string.volume_dialog_ringer_guidance_ring,
+ Utils.formatPercentage(level.toLong(), levelMax.toLong()),
+ )
+ } else {
+ null
+ }
+ }
+
+ RINGER_MODE_SILENT ->
+ applicationContext.getString(
+ internalR.string.volume_dialog_ringer_guidance_silent
+ )
+
+ RINGER_MODE_VIBRATE ->
+ applicationContext.getString(
+ internalR.string.volume_dialog_ringer_guidance_vibrate
+ )
+
+ else ->
+ applicationContext.getString(
+ internalR.string.volume_dialog_ringer_guidance_vibrate
+ )
+ }
+ toastText?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT).show() }
+ interactor.updateToastCount(seenToastCount)
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(): VolumeDialogRingerDrawerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 8039e00159f0..073781e6101d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -271,6 +271,12 @@ public final class WMShell implements
// No op.
}
}, mSysUiMainExecutor);
+ pip.addOnIsInPipStateChangedListener((isInPip) -> {
+ if (!isInPip) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+ });
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 2b167e4c5da4..65b62737b692 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -56,6 +56,7 @@ import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ZenModeController
@@ -128,6 +129,7 @@ class ClockEventControllerTest : SysuiTestCase() {
@Mock private lateinit var largeClockEvents: ClockFaceEvents
@Mock private lateinit var parentView: View
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var zenModeController: ZenModeController
private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -153,6 +155,7 @@ class ClockEventControllerTest : SysuiTestCase() {
.thenReturn(ClockFaceConfig(tickRate = ClockTickRate.PER_MINUTE))
whenever(smallClockController.theme).thenReturn(ThemeConfig(true, null))
whenever(largeClockController.theme).thenReturn(ThemeConfig(true, null))
+ whenever(userTracker.userId).thenReturn(1)
zenModeRepository.addMode(MANUAL_DND_INACTIVE)
@@ -177,6 +180,7 @@ class ClockEventControllerTest : SysuiTestCase() {
withDeps.featureFlags,
zenModeController,
kosmos.zenModeInteractor,
+ userTracker,
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index fb376ce3ca40..3ddd4b58211d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -289,6 +289,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
+ whenever(controller.sessionToken).thenReturn(session.sessionToken)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -1599,6 +1600,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testTooManyCompactActions_isTruncated() {
// GIVEN a notification where too many compact actions were specified
@@ -1635,6 +1637,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
.isEqualTo(LegacyMediaDataManagerImpl.MAX_COMPACT_ACTIONS)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testTooManyNotificationActions_isTruncated() {
// GIVEN a notification where too many notification actions are added
@@ -1670,6 +1673,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
.isEqualTo(LegacyMediaDataManagerImpl.MAX_NOTIFICATION_ACTIONS)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
@@ -1703,6 +1707,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
@@ -1746,6 +1751,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
@@ -1778,6 +1784,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_connecting() {
val stateActions = PlaybackState.ACTION_PLAY
@@ -1797,6 +1804,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
.isEqualTo(context.getString(R.string.controls_media_button_connecting))
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
@@ -1835,6 +1843,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(actions.reservePrev).isTrue()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_playPause_hasButton() {
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
@@ -1998,6 +2007,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
whenever(controller.playbackState).thenReturn(null)
@@ -2056,6 +2066,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
assertThat(mediaDataCaptor.value.isClearable).isFalse()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
@@ -2086,6 +2097,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
@@ -2104,6 +2116,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 7d364bd832f2..e5483c0980c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -103,7 +103,6 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -113,6 +112,7 @@ import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -312,6 +312,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
}
verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
mediaControllerFactory.setControllerForToken(session.sessionToken, controller)
+ whenever(controller.sessionToken).thenReturn(session.sessionToken)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
whenever(controller.metadata).thenReturn(metadataBuilder.build())
@@ -596,7 +597,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
fun testOnNotificationAdded_emptyTitle_hasPlaceholder() {
// When the manager has a notification with an empty title, and the app is not
// required to include a non-empty title
- val mockPackageManager = mock(PackageManager::class.java)
+ val mockPackageManager = mock<PackageManager>()
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
whenever(controller.metadata)
@@ -626,7 +627,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
fun testOnNotificationAdded_blankTitle_hasPlaceholder() {
// GIVEN that the manager has a notification with a blank title, and the app is not
// required to include a non-empty title
- val mockPackageManager = mock(PackageManager::class.java)
+ val mockPackageManager = mock<PackageManager>()
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
whenever(controller.metadata)
@@ -656,7 +657,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
fun testOnNotificationAdded_emptyMetadata_usesNotificationTitle() {
// When the app sets the metadata title fields to empty strings, but does include a
// non-blank notification title
- val mockPackageManager = mock(PackageManager::class.java)
+ val mockPackageManager = mock<PackageManager>()
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
whenever(controller.metadata)
@@ -1610,6 +1611,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testTooManyCompactActions_isTruncated() {
// GIVEN a notification where too many compact actions were specified
@@ -1646,6 +1648,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
.isEqualTo(MediaDataProcessor.MAX_COMPACT_ACTIONS)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testTooManyNotificationActions_isTruncated() {
// GIVEN a notification where too many notification actions are added
@@ -1681,6 +1684,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
.isEqualTo(MediaDataProcessor.MAX_NOTIFICATION_ACTIONS)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_noState_usesNotification() {
val desc = "Notification Action"
@@ -1714,6 +1718,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_hasPrevNext() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
@@ -1757,6 +1762,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_noPrevNext_usesCustom() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
@@ -1789,6 +1795,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_connecting() {
val stateActions = PlaybackState.ACTION_PLAY
@@ -1874,6 +1881,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
.isNotEqualTo(firstSemanticActions.prevOrCustom?.icon)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
@@ -1912,6 +1920,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(actions.reservePrev).isTrue()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackActions_playPause_hasButton() {
val stateActions = PlaybackState.ACTION_PLAY_PAUSE
@@ -2074,6 +2083,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testPlaybackStateNull_Pause_keyExists_callsListener() {
whenever(controller.playbackState).thenReturn(null)
@@ -2132,6 +2142,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(mediaDataCaptor.value.isClearable).isFalse()
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_notifRemoved_setToResume() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
@@ -2162,6 +2173,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
)
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
@@ -2180,6 +2192,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_BUTTON_MEDIA3)
@Test
fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 4b648a3e76e7..d1e4f646a382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone.fragment;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS;
-import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
@@ -61,6 +60,7 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
@@ -156,7 +156,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableNone() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -167,7 +167,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -185,7 +185,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -215,7 +215,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
// GIVEN the status bar hides the system info via disable flags, while there is no event
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -232,7 +232,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -248,7 +248,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
// GIVEN the status bar is not disabled
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -272,7 +272,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableNotifications() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -290,7 +290,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableNotifications_doesNothingWhenFlagEnabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -308,7 +308,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableClock() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -326,7 +326,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME)
public void testDisableClock_doesNothingWhenFlagEnabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -345,7 +345,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_shadeOpenAndShouldHide_everythingHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -363,7 +363,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_shadeOpenButNotShouldHide_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -382,7 +382,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
/** Regression test for b/279790651. */
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -410,7 +410,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_notTransitioningToOccluded_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -426,7 +426,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_isTransitioningToOccluded_everythingHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -442,7 +442,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -473,7 +473,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_noOngoingCall_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -485,7 +485,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -498,7 +498,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -511,7 +511,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_hasOngoingCallButAlsoHun_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -524,7 +524,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_ongoingCallEnded_chipHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -548,7 +548,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
@@ -565,7 +565,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
public void screenSharingChipsDisabled_ignoresNewCallback() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -599,7 +599,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void noOngoingActivity_chipHidden() {
resumeAndGetFragment();
@@ -617,7 +617,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
resumeAndGetFragment();
@@ -634,8 +634,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@EnableFlags({
FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
StatusBarNotifChips.FLAG_NAME,
- FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
- public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() {
+ StatusBarRootModernization.FLAG_NAME})
+ public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
resumeAndGetFragment();
assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
@@ -660,7 +660,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
resumeAndGetFragment();
@@ -674,7 +674,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
resumeAndGetFragment();
@@ -689,7 +689,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -706,7 +706,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -724,7 +724,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -741,7 +741,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -759,7 +759,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
resumeAndGetFragment();
@@ -782,7 +782,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
resumeAndGetFragment();
@@ -805,7 +805,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void secondaryOngoingActivityEnded_chipHidden() {
resumeAndGetFragment();
@@ -828,7 +828,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
@@ -847,7 +847,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
// Enable animations for testing so that we can verify we still aren't animating
@@ -866,7 +866,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+ @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -899,7 +899,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -933,7 +933,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
resumeAndGetFragment();
@@ -947,7 +947,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void isHomeStatusBarAllowedByScene_true_everythingShown() {
resumeAndGetFragment();
@@ -961,7 +961,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -979,7 +979,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@EnableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -997,7 +997,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
resumeAndGetFragment();
@@ -1011,7 +1011,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -1023,7 +1023,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -1035,7 +1035,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
@@ -1046,7 +1046,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
@@ -1100,7 +1100,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -1123,7 +1123,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Test
@DisableSceneContainer
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -1159,7 +1159,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ @DisableFlags(StatusBarRootModernization.FLAG_NAME)
public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
index c435d3d99680..37671e0bc175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -21,9 +21,9 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.statusbar.core.StatusBarRootModernization
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -38,7 +38,7 @@ private const val INITIAL_ALPHA = 1f
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
-@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+@DisableFlags(StatusBarRootModernization.FLAG_NAME)
class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
private val view = View(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index c48898aad087..2e0b7c69f092 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -67,12 +67,12 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -96,6 +96,8 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -137,6 +139,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+ private val userRepository = kosmos.fakeUserRepository
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -159,7 +162,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
logcatTableLogBuffer(kosmos, "test")
}
- whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+ whenever(
+ wifiPickerTrackerFactory.create(
+ any(),
+ any(),
+ capture(wifiPickerTrackerCallback),
+ any(),
+ )
+ )
.thenReturn(wifiPickerTracker)
// For convenience, set up the subscription info callbacks
@@ -188,6 +198,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
wifiRepository =
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
mainExecutor,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 44e1437b909e..d823bf57c824 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -16,13 +16,19 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.content.Context
+import android.content.pm.UserInfo
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.LogBuffer
@@ -33,12 +39,10 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRep
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.fakeSystemClock
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -56,7 +60,12 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
/**
* Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
@@ -67,8 +76,9 @@ import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest : SysuiTestCase() {
+class WifiRepositoryImplTest() : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val userRepository = kosmos.fakeUserRepository
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
@@ -76,6 +86,8 @@ class WifiRepositoryImplTest : SysuiTestCase() {
// inside each test case without needing to manually recreate the repository.
private val underTest: WifiRepositoryImpl by lazy {
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
executor,
dispatcher,
@@ -101,7 +113,8 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
- whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor), any()))
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+ whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
.thenReturn(wifiPickerTracker)
}
@@ -1203,6 +1216,95 @@ class WifiRepositoryImplTest : SysuiTestCase() {
assertThat(latest).isEmpty()
}
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+ }
+
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+ val otherUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(ANOTHER_USER_ID),
+ otherUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+ val otherUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(otherUserContext).isEqualTo(otherUserMockContext)
+ verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ }
+
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+ val otherUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(ANOTHER_USER_ID),
+ otherUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+
+ verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+ }
+
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
testScope.runCurrent()
return callbackCaptor.value
@@ -1231,5 +1333,20 @@ class WifiRepositoryImplTest : SysuiTestCase() {
private companion object {
const val TITLE = "AB"
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PROFILE,
+ )
+
+ private const val ANOTHER_USER_ID = 1
+ private val ANOTHER_USER =
+ UserInfo(
+ /* id= */ ANOTHER_USER_ID,
+ /* name= */ "another user",
+ /* flags= */ UserInfo.FLAG_PROFILE,
+ )
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 2dcd275f0103..f6ff4c4a057e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -35,6 +36,9 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
private var pendingLockscreenEnabled = _isLockscreenEnabled.value
+ override val deviceUnlockStatus =
+ MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
override suspend fun isLockscreenEnabled(): Boolean {
_isLockscreenEnabled.value = pendingLockscreenEnabled
return isLockscreenEnabled.value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 8922b2f5c5ef..be84e3316f5b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -19,25 +19,23 @@ 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.flags.fakeSystemPropertiesHelper
-import com.android.systemui.flags.systemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.deviceUnlockedInteractor by Fixture {
DeviceUnlockedInteractor(
- applicationScope = applicationCoroutineScope,
- authenticationInteractor = authenticationInteractor,
- deviceEntryRepository = deviceEntryRepository,
- trustInteractor = trustInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- powerInteractor = powerInteractor,
- biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
- systemPropertiesHelper = fakeSystemPropertiesHelper,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
- )
+ authenticationInteractor = authenticationInteractor,
+ repository = deviceEntryRepository,
+ trustInteractor = trustInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ powerInteractor = powerInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
+ )
+ .apply { activateIn(testScope) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
index f49e3771763a..b3be2c09c6f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -22,6 +22,7 @@ import android.os.Handler
import android.os.looper
import androidx.media3.session.CommandButton
import androidx.media3.session.MediaController
+import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionToken
import com.android.systemui.Flags
import com.android.systemui.graphics.imageLoader
@@ -30,7 +31,11 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.util.concurrency.execution
import com.google.common.collect.ImmutableList
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@@ -46,10 +51,22 @@ var Kosmos.media3ActionFactory: Media3ActionFactory by
mock<MediaController>().also {
whenever(it.customLayout).thenReturn(customLayout)
whenever(it.sessionExtras).thenReturn(Bundle())
+ whenever(it.isCommandAvailable(any())).thenReturn(true)
+ whenever(it.isSessionCommandAvailable(any<SessionCommand>())).thenReturn(true)
}
fakeMediaControllerFactory.setMedia3Controller(media3Controller)
fakeSessionTokenFactory.setMedia3SessionToken(mock<SessionToken>())
}
+
+ val runnableCaptor = argumentCaptor<Runnable>()
+ val handler =
+ mock<Handler> {
+ on { post(runnableCaptor.capture()) } doAnswer
+ {
+ runnableCaptor.lastValue.run()
+ true
+ }
+ }
Media3ActionFactory(
context = applicationContext,
imageLoader = imageLoader,
@@ -57,7 +74,8 @@ var Kosmos.media3ActionFactory: Media3ActionFactory by
tokenFactory = fakeSessionTokenFactory,
logger = mediaLogger,
looper = looper,
- handler = Handler(looper),
+ handler = handler,
bgScope = testScope,
+ execution = execution,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
index 513d4e418a41..1395b1818b30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
@@ -16,8 +16,10 @@
package com.android.systemui.qs.panels.data.repository
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.settings.userFileManager
import com.android.systemui.user.data.repository.userRepository
@@ -27,6 +29,8 @@ val Kosmos.qsPreferencesRepository by
userFileManager,
userRepository,
defaultLargeTilesRepository,
- testDispatcher
+ testDispatcher,
+ FakeLogBuffer.Factory.create(),
+ broadcastDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
index d1b613fe7f6e..f63698a3f2f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
@@ -21,6 +21,7 @@ import android.content.res.mainResources
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
import com.android.systemui.shade.data.repository.shadeRepository
@@ -56,4 +57,5 @@ fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) {
}
mainResources.configuration.updateFrom(config)
fakeConfigurationRepository.onConfigurationChange(config)
+ runCurrent()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index c3c3cce5cf68..dae66d42b2bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -41,6 +41,7 @@ class FakeMobileConnectionRepository(
override val isGsm = MutableStateFlow(false)
override val cdmaLevel = MutableStateFlow(0)
override val primaryLevel = MutableStateFlow(0)
+ override val satelliteLevel = MutableStateFlow(0)
override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
override val dataActivityDirection =
MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1808a5f99f4e..85d582a27faf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -72,6 +72,10 @@ class FakeUserRepository @Inject constructor() : UserRepository {
override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
_isSecondaryUserLogoutEnabled.asStateFlow()
+ private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false)
+ override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+ _isLogoutToSystemUserEnabled.asStateFlow()
+
override var mainUserId: Int = MAIN_USER_ID
override var lastSelectedNonGuestUserId: Int = mainUserId
@@ -123,6 +127,17 @@ class FakeUserRepository @Inject constructor() : UserRepository {
logOutSecondaryUserCallCount++
}
+ fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) {
+ _isLogoutToSystemUserEnabled.value = logoutEnabled
+ }
+
+ var logOutToSystemUserCallCount: Int = 0
+ private set
+
+ override suspend fun logOutToSystemUser() {
+ logOutToSystemUserCallCount++
+ }
+
fun setUserInfos(infos: List<UserInfo>) {
_userInfos.value = infos
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt
new file mode 100644
index 000000000000..d42de1e6d0df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.data.repository
+
+class FakeVolumeDialogRingerFeedbackRepository : VolumeDialogRingerFeedbackRepository {
+
+ private var seenToastCount = 0
+
+ override suspend fun getToastCount(): Int {
+ return seenToastCount
+ }
+
+ override suspend fun updateToastCount(toastCount: Int) {
+ seenToastCount = toastCount
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
new file mode 100644
index 000000000000..44371b4615df
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeVolumeDialogRingerFeedbackRepository by
+ Kosmos.Fixture { FakeVolumeDialogRingerFeedbackRepository() }
+val Kosmos.volumeDialogRingerFeedbackRepository by
+ Kosmos.Fixture { fakeVolumeDialogRingerFeedbackRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
index 1addd91d2ec2..a494d04ec741 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
import com.android.systemui.volume.data.repository.audioSystemRepository
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.data.repository.fakeVolumeDialogRingerFeedbackRepository
val Kosmos.volumeDialogRingerInteractor by
Kosmos.Fixture {
@@ -29,5 +30,6 @@ val Kosmos.volumeDialogRingerInteractor by
volumeDialogStateInteractor = volumeDialogStateInteractor,
controller = volumeDialogController,
audioSystemRepository = audioSystemRepository,
+ ringerFeedbackRepository = fakeVolumeDialogRingerFeedbackRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
index db1c01a8698c..c8ba551c518a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -16,20 +16,24 @@
package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
import com.android.systemui.volume.dialog.shared.volumeDialogLogger
val Kosmos.volumeDialogRingerDrawerViewModel by
Kosmos.Fixture {
VolumeDialogRingerDrawerViewModel(
+ applicationContext = applicationContext,
backgroundDispatcher = testDispatcher,
coroutineScope = applicationCoroutineScope,
interactor = volumeDialogRingerInteractor,
vibrator = vibratorHelper,
volumeDialogLogger = volumeDialogLogger,
+ visibilityInteractor = volumeDialogVisibilityInteractor,
)
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index d8f2b705d539..3ed8b0a748e1 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -16,7 +16,6 @@
package android.platform.test.ravenwood;
import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.SYSTEM;
import android.annotation.NonNull;
@@ -61,17 +60,14 @@ public final class RavenwoodConfig {
* Unless the test author requests differently, run as "nobody", and give each collection of
* tests its own unique PID.
*/
- int mUid = NOBODY_UID;
+ int mUid = FIRST_APPLICATION_UID;
int mPid = sNextPid.getAndIncrement();
String mTestPackageName;
String mTargetPackageName;
- int mMinSdkLevel;
int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
- boolean mProvideMainThread = false;
-
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
final List<Class<?>> mServicesRequired = new ArrayList<>();
@@ -108,20 +104,18 @@ public final class RavenwoodConfig {
}
/**
- * Configure the identity of this process to be the system UID for the duration of the
- * test. Has no effect on non-Ravenwood environments.
+ * @deprecated no longer used. We always use an app UID.
*/
+ @Deprecated
public Builder setProcessSystem() {
- mConfig.mUid = SYSTEM_UID;
return this;
}
/**
- * Configure the identity of this process to be an app UID for the duration of the
- * test. Has no effect on non-Ravenwood environments.
+ * @deprecated no longer used. We always use an app UID.
*/
+ @Deprecated
public Builder setProcessApp() {
- mConfig.mUid = FIRST_APPLICATION_UID;
return this;
}
@@ -144,14 +138,6 @@ public final class RavenwoodConfig {
}
/**
- * Configure the min SDK level of the test.
- */
- public Builder setMinSdkLevel(int sdkLevel) {
- mConfig.mMinSdkLevel = sdkLevel;
- return this;
- }
-
- /**
* Configure the target SDK level of the test.
*/
public Builder setTargetSdkLevel(int sdkLevel) {
@@ -160,14 +146,10 @@ public final class RavenwoodConfig {
}
/**
- * Configure a "main" thread to be available for the duration of the test, as defined
- * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
- *
- * @deprecated
+ * @deprecated no longer used. Main thread is always available.
*/
@Deprecated
public Builder setProvideMainThread(boolean provideMainThread) {
- mConfig.mProvideMainThread = provideMainThread;
return this;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 3d6ac0f37050..bfa3802ce583 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -112,20 +112,18 @@ public final class RavenwoodRule implements TestRule {
}
/**
- * Configure the identity of this process to be the system UID for the duration of the
- * test. Has no effect on non-Ravenwood environments.
+ * @deprecated no longer used. We always use an app UID.
*/
+ @Deprecated
public Builder setProcessSystem() {
- mBuilder.setProcessSystem();
return this;
}
/**
- * Configure the identity of this process to be an app UID for the duration of the
- * test. Has no effect on non-Ravenwood environments.
+ * @deprecated no longer used. We always use an app UID.
*/
+ @Deprecated
public Builder setProcessApp() {
- mBuilder.setProcessApp();
return this;
}
@@ -139,14 +137,10 @@ public final class RavenwoodRule implements TestRule {
}
/**
- * Configure a "main" thread to be available for the duration of the test, as defined
- * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
- *
- * @deprecated
+ * @deprecated no longer used. Main thread is always available.
*/
@Deprecated
public Builder setProvideMainThread(boolean provideMainThread) {
- mBuilder.setProvideMainThread(provideMainThread);
return this;
}
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
index d232ef2076be..c85bd23db893 100644
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
@@ -18,6 +18,7 @@ package android.util;
import android.util.Log.Level;
import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
import java.io.PrintStream;
@@ -35,6 +36,9 @@ public class Log_host {
}
public static int println_native(int bufID, int priority, String tag, String msg) {
+ if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) {
+ return msg.length(); // No verbose logging.
+ }
final String buffer;
switch (bufID) {
case Log.LOG_ID_MAIN: buffer = "main"; break;
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 82be2c0db24e..c196a09e18d1 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -15,7 +15,9 @@ com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistoryIterator
com.android.internal.os.Clock
com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter_ravenwood
com.android.internal.os.LongMultiStateCounter
+com.android.internal.os.LongMultiStateCounter_ravenwood
com.android.internal.os.MonotonicClock
com.android.internal.os.PowerProfile
com.android.internal.os.PowerStats
@@ -143,6 +145,7 @@ android.os.LocaleList
android.os.Looper
android.os.Message
android.os.MessageQueue
+android.os.MessageQueue_ravenwood
android.os.PackageTagsList
android.os.Parcel
android.os.ParcelFileDescriptor
@@ -235,6 +238,7 @@ android.database.Cursor
android.database.CursorIndexOutOfBoundsException
android.database.CursorJoiner
android.database.CursorWindow
+android.database.CursorWindow_ravenwood
android.database.CursorWrapper
android.database.DataSetObservable
android.database.DataSetObserver
@@ -370,4 +374,3 @@ android.app.compat.*
com.android.server.compat.*
com.android.internal.compat.*
android.app.AppCompatCallbacks
-
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 617cca9d3075..d6fc6e461edc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -27,6 +27,8 @@ import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -44,11 +46,14 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.Nullable;
+
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
+import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
@@ -187,6 +192,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private final AccessibilityManagerService mAms;
+ private final InputManager mInputManager;
+
private final SparseArray<EventStreamTransformation> mEventHandler;
private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
@@ -228,6 +235,47 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private MotionEvent mLastActiveDeviceMotionEvent = null;
+ private boolean mKeyGestureEventHandlerInstalled = false;
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ final int displayId = isDisplayIdValid(event.getDisplayId())
+ ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_IN);
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_OUT);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true;
+ default -> false;
+ };
+ }
+ };
+
private static MotionEvent cancelMotion(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -287,6 +335,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mInputManager = context.getSystemService(InputManager.class);
mEventHandler = eventHandler;
}
@@ -723,6 +772,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
createMagnificationGestureHandler(displayId, displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+
+ if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()
+ && !mKeyGestureEventHandlerInstalled) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = true;
+ }
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
@@ -842,6 +897,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mMouseKeysInterceptor.onDestroy();
mMouseKeysInterceptor = null;
}
+
+ if (mKeyGestureEventHandlerInstalled) {
+ mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = false;
+ }
}
private MagnificationGestureHandler createMagnificationGestureHandler(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 974cba2450f4..86d3ee6d1257 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5071,6 +5071,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@EnforcePermission(MANAGE_ACCESSIBILITY)
public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
isAccessibilityServiceWarningRequired_enforcePermission();
+ if (info == null) {
+ Log.e(LOG_TAG, "Called isAccessibilityServiceWarningRequired with null service info");
+ return true;
+ }
+
final ComponentName componentName = info.getComponentName();
// Warning is not required if the service is already enabled.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index d40e7476f7ec..51c4305061f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import android.accessibilityservice.MagnificationConfig;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -101,6 +102,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
/** Whether the platform supports window magnification feature. */
private final boolean mSupportWindowMagnification;
+ private final MagnificationScaleStepProvider mScaleStepProvider;
private final Executor mBackgroundExecutor;
@@ -131,6 +133,14 @@ public class MagnificationController implements MagnificationConnectionManager.C
.UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
new SparseArray<>();
+ // Direction magnifier scale can be altered.
+ public static final int ZOOM_DIRECTION_IN = 0;
+ public static final int ZOOM_DIRECTION_OUT = 1;
+
+ @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT})
+ public @interface ZoomDirection {
+ }
+
/**
* A callback to inform the magnification transition result on the given display.
*/
@@ -144,6 +154,41 @@ public class MagnificationController implements MagnificationConnectionManager.C
void onResult(int displayId, boolean success);
}
+
+ /**
+ * An interface to configure how much the magnification scale should be affected when moving in
+ * steps.
+ */
+ public interface MagnificationScaleStepProvider {
+ /**
+ * Calculate the next value given which direction (in/out) to adjust the magnification
+ * scale.
+ *
+ * @param currentScale The current magnification scale value.
+ * @param direction Whether to zoom in or out.
+ * @return The next scale value.
+ */
+ float nextScaleStep(float currentScale, @ZoomDirection int direction);
+ }
+
+ public static class DefaultMagnificationScaleStepProvider implements
+ MagnificationScaleStepProvider {
+ // Factor of magnification scale. For example, when this value is 1.189, scale
+ // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ...
+ // Note: this value is 2.0 ^ (1 / 4).
+ public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f;
+
+ @Override
+ public float nextScaleStep(float currentScale, @ZoomDirection int direction) {
+ final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1;
+ final long scaleIndex = Math.round(
+ Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR));
+ final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR,
+ scaleIndex + stepDelta);
+ return MagnificationScaleProvider.constrainScale(nextScale);
+ }
+ }
+
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, MagnificationScaleProvider scaleProvider,
Executor backgroundExecutor) {
@@ -156,6 +201,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
+ mScaleStepProvider = new DefaultMagnificationScaleStepProvider();
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
@@ -891,6 +937,37 @@ public class MagnificationController implements MagnificationConnectionManager.C
return isActivated;
}
+ /**
+ * Scales the magnifier on the given display one step in/out based on the zoomIn param.
+ *
+ * @param displayId The logical display id.
+ * @param direction Whether the scale should be zoomed in or out.
+ * @return {@code true} if the magnification scale was affected.
+ */
+ public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
+ if (getFullScreenMagnificationController().isActivated(displayId)) {
+ final float magnificationScale = getFullScreenMagnificationController().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getFullScreenMagnificationController().setScaleAndCenter(displayId,
+ nextMagnificationScale,
+ Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
+ final float magnificationScale = getMagnificationConnectionManager().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ return false;
+ }
+
private final class DisableMagnificationCallback implements
MagnificationAnimationCallback {
private final TransitionCallBack mTransitionCallBack;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 268e56487c4b..f13e22950e2d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -29,7 +29,7 @@ import android.app.appfunctions.AppFunctionManagerHelper;
import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
-import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
@@ -156,11 +156,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
mCallerValidator.verifyTargetUserHandle(
requestInternal.getUserHandle(), validatedCallingPackage);
} catch (SecurityException exception) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- exception.getMessage(),
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_DENIED,
+ exception.getMessage()));
return null;
}
@@ -180,7 +179,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
safeExecuteAppFunctionCallback,
executeAppFunctionCallback.asBinder());
} catch (Exception e) {
- safeExecuteAppFunctionCallback.onResult(
+ safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(e));
}
});
@@ -198,22 +197,19 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
UserHandle targetUser = requestInternal.getUserHandle();
// TODO(b/354956319): Add and honor the new enterprise policies.
if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
"Cannot run on a device with a device owner or from the managed"
- + " profile.",
- /* extras= */ null));
+ + " profile."));
return;
}
String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
if (TextUtils.isEmpty(targetPackageName)) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
- "Target package name cannot be empty.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_INVALID_ARGUMENT,
+ "Target package name cannot be empty."));
return;
}
@@ -253,11 +249,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
mInternalServiceHelper.resolveAppFunctionService(
targetPackageName, targetUser);
if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Cannot find the target service."));
return;
}
bindAppFunctionServiceUnchecked(
@@ -272,7 +267,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
})
.exceptionally(
ex -> {
- safeExecuteAppFunctionCallback.onResult(
+ safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(ex));
return null;
});
@@ -446,11 +441,9 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
if (!bindServiceResult) {
Slog.e(TAG, "Failed to bind to the AppFunctionService");
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
- "Failed to bind the AppFunctionService.",
- /* extras= */ null));
+ safeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
+ "Failed to bind the AppFunctionService."));
}
}
@@ -459,22 +452,21 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
.getSystemService(AppSearchManager.class);
}
- private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
+ private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) {
if (e instanceof CompletionException) {
e = e.getCause();
}
- int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+ int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR;
if (e instanceof AppSearchException appSearchException) {
resultCode =
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
appSearchException.getResultCode());
} else if (e instanceof SecurityException) {
- resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+ resultCode = AppFunctionException.ERROR_DENIED;
} else if (e instanceof DisabledAppFunctionException) {
- resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
+ resultCode = AppFunctionException.ERROR_DISABLED;
}
- return ExecuteAppFunctionResponse.newFailure(
- resultCode, e.getMessage(), /* extras= */ null);
+ return new AppFunctionException(resultCode, e.getMessage());
}
private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -485,13 +477,13 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
switch (resultCode) {
case AppSearchResult.RESULT_NOT_FOUND:
- return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND;
+ return AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
case AppSearchResult.RESULT_INVALID_ARGUMENT:
case AppSearchResult.RESULT_INTERNAL_ERROR:
case AppSearchResult.RESULT_SECURITY_ERROR:
// fall-through
}
- return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+ return AppFunctionException.ERROR_SYSTEM_ERROR;
}
private void registerAppSearchObserver(@NonNull TargetUser user) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index 129be65f3153..c689bb92f8f7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -17,6 +17,7 @@ package com.android.server.appfunctions;
import android.annotation.NonNull;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.AppFunctionException;
import android.app.appfunctions.ExecuteAppFunctionResponse;
import android.app.appfunctions.IAppFunctionService;
import android.app.appfunctions.ICancellationCallback;
@@ -57,17 +58,22 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
mCancellationCallback,
new IExecuteAppFunctionCallback.Stub() {
@Override
- public void onResult(ExecuteAppFunctionResponse response) {
+ public void onSuccess(ExecuteAppFunctionResponse response) {
mSafeExecuteAppFunctionCallback.onResult(response);
serviceUsageCompleteListener.onCompleted();
}
+
+ @Override
+ public void onError(AppFunctionException error) {
+ mSafeExecuteAppFunctionCallback.onError(error);
+ serviceUsageCompleteListener.onCompleted();
+ }
});
} catch (Exception e) {
- mSafeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- e.getMessage(),
- /* extras= */ null));
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(
+ AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ e.getMessage()));
serviceUsageCompleteListener.onCompleted();
}
}
@@ -75,11 +81,9 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
@Override
public void onFailedToConnect() {
Slog.e(TAG, "Failed to connect to service");
- mSafeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
- "Failed to connect to AppFunctionService",
- /* extras= */ null));
+ mSafeExecuteAppFunctionCallback.onError(
+ new AppFunctionException(AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+ "Failed to connect to AppFunctionService"));
}
@Override
diff --git a/services/art-profile b/services/art-profile
index 6fa4c88cb1f6..ce1e2c6f1397 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -5657,7 +5657,7 @@ Lcom/android/server/utils/WatchedSparseSetArray;
Lcom/android/server/utils/Watcher;
Lcom/android/server/vibrator/VibratorController$NativeWrapper;
Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
Lcom/android/server/vibrator/VibratorManagerService;
Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
Lcom/android/server/vr/VrManagerService;
diff --git a/services/art-wear-profile b/services/art-wear-profile
index 47bdb1385137..1e3090f9bf00 100644
--- a/services/art-wear-profile
+++ b/services/art-wear-profile
@@ -1330,7 +1330,7 @@ Lcom/android/server/utils/WatchedSparseSetArray;
Lcom/android/server/utils/Watcher;
Lcom/android/server/vibrator/VibratorController$NativeWrapper;
Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
Lcom/android/server/vibrator/VibratorManagerService;
Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
Lcom/android/server/vr/VrManagerService;
@@ -24948,7 +24948,7 @@ PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;-><init>()V
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->cancelSynced()V
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getCapabilities()J
PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getVibratorIds()[I
-PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)V
+PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;-><init>(Lcom/android/server/vibrator/VibratorManagerService;)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;->onComplete(IJ)V
PLcom/android/server/vibrator/VibratorManagerService$VibrationRecords;-><init>(II)V
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ddccb3731cc1..466d477992b3 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -281,6 +281,9 @@ public class UserBackupManagerService {
private static final int SCHEDULE_FILE_VERSION = 1;
public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
+
+ public static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
+
public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
// Pseudoname that we use for the Package Manager metadata "package".
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index f24a3c1afc86..508b62cd83f0 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -21,6 +21,7 @@ import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -75,6 +76,12 @@ public class BackupEligibilityRules {
systemPackagesAllowedForProfileUser,
Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
+ static {
+ if (UserManager.isHeadlessSystemUserMode()) {
+ systemPackagesAllowedForNonSystemUsers.add(TELEPHONY_PROVIDER_PACKAGE);
+ }
+ }
+
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final int mUserId;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3ccad16073a7..aea16b08df49 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -158,6 +158,7 @@ java_library_static {
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V3-java",
"app-compat-annotations",
+ "art_exported_aconfig_flags_lib",
"framework-tethering.stubs.module_lib",
"keepanno-annotations",
"service-art.stubs.system_server",
@@ -237,6 +238,7 @@ java_library_static {
"connectivity_flags_lib",
"device_config_service_flags_java",
"dreams_flags_lib",
+ "aconfig_flags_java",
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 72a9a2d6de26..fa228627c255 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -88,6 +88,7 @@ import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -440,6 +441,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private boolean[] mCarrierRoamingNtnEligible = null;
private List<IntArray> mCarrierRoamingNtnAvailableServices;
+ private NtnSignalStrength[] mCarrierRoamingNtnSignalStrength;
// Local cache to check if Satellite Modem is enabled
private AtomicBoolean mIsSatelliteEnabled;
@@ -745,6 +747,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mSCBMDuration = copyOf(mSCBMDuration, mNumPhones);
mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
+ if (mCarrierRoamingNtnSignalStrength != null) {
+ mCarrierRoamingNtnSignalStrength = copyOf(
+ mCarrierRoamingNtnSignalStrength, mNumPhones);
+ } else {
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[mNumPhones];
+ }
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
@@ -807,6 +815,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
}
}
@@ -883,6 +893,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierRoamingNtnMode = new boolean[numPhones];
mCarrierRoamingNtnEligible = new boolean[numPhones];
mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[numPhones];
mIsSatelliteEnabled = new AtomicBoolean();
mWasSatelliteEnabledNotified = new AtomicBoolean();
@@ -932,6 +943,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1565,6 +1578,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(
+ mCarrierRoamingNtnSignalStrength[r.phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -3803,6 +3825,44 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+
+ /**
+ * Notify external listeners that carrier roaming non-terrestrial network
+ * signal strength changed.
+ * @param subId subscription ID.
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ */
+ public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) {
+ log("nnotifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnSignalStrengthChanged: "
+ + "subId=" + subId + " ntnSignalStrength=" + ntnSignalStrength.getLevel());
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierRoamingNtnSignalStrength[phoneId] = ntnSignalStrength;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3858,6 +3918,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("mSCBMDuration=" + mSCBMDuration[i]);
pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
+ pw.println("mCarrierRoamingNtnSignalStrength="
+ + mCarrierRoamingNtnSignalStrength[i]);
// We need to obfuscate package names, and primitive arrays' native toString is ugly
Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index b18810564d88..000000000000
--- a/services/core/java/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hainingc@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index aa9ac6c36784..2eb9f3cb600f 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,6 +124,7 @@ import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.net.BaseNetworkObserver;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -195,6 +196,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver();
private final PowerAttributor mPowerAttributor;
+ private final PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
private volatile boolean mMonitorEnabled = true;
private boolean mRailsStatsCollectionEnabled = true;
@@ -617,6 +619,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
BatteryConsumer.POWER_COMPONENT_ANY,
Flags.streamlinedMiscBatteryStats());
+ mStats.setMoveWscLoggingToNotifierEnabled(
+ mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled());
+
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
mCpuWakeupStats.systemServicesReady();
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index b0f880710eb6..8a128582c507 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -593,7 +593,7 @@ class BroadcastController {
originalStickyCallingUid, BackgroundStartPrivileges.NONE,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
null /* filterExtrasForReceiver */,
- broadcast.originalCallingAppProcessState);
+ broadcast.originalCallingAppProcessState, mService.mPlatformCompat);
queue.enqueueBroadcastLocked(r);
}
}
@@ -1631,7 +1631,7 @@ class BroadcastController {
receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId,
backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
- callerAppProcessState);
+ callerAppProcessState, mService.mPlatformCompat);
broadcastSentEventRecord.setBroadcastRecord(r);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index f908c67d7ec9..116aeeaa35c1 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -42,6 +42,9 @@ import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.BroadcastOptions.DeliveryGroupPolicy;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
@@ -55,10 +58,12 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.PrintWriterPrinter;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.compat.PlatformCompat;
import dalvik.annotation.optimization.NeverCompile;
@@ -77,6 +82,16 @@ import java.util.function.BiFunction;
* An active intent broadcast.
*/
final class BroadcastRecord extends Binder {
+ /**
+ * Limit the scope of the priority values to the process level. This means that priority values
+ * will only influence the order of broadcast delivery within the same process.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
+ @Overridable
+ @VisibleForTesting
+ static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
+
final @NonNull Intent intent; // the original intent that generated us
final @Nullable ComponentName targetComp; // original component name set on the intent
final @Nullable ProcessRecord callerApp; // process that sent this
@@ -417,13 +432,13 @@ final class BroadcastRecord extends Binder {
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- int callerAppProcessState) {
+ int callerAppProcessState, PlatformCompat platformCompat) {
this(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid,
callingUid, callerInstantApp, resolvedType, requiredPermissions,
excludedPermissions, excludedPackages, appOp, options, receivers, resultToApp,
resultTo, resultCode, resultData, resultExtras, serialized, sticky,
initialSticky, userId, -1, backgroundStartPrivileges, timeoutExempt,
- filterExtrasForReceiver, callerAppProcessState);
+ filterExtrasForReceiver, callerAppProcessState, platformCompat);
}
BroadcastRecord(BroadcastQueue _queue,
@@ -439,7 +454,7 @@ final class BroadcastRecord extends Binder {
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- int callerAppProcessState) {
+ int callerAppProcessState, PlatformCompat platformCompat) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -466,7 +481,8 @@ final class BroadcastRecord extends Binder {
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
- blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
+ blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(
+ receivers, _serialized, platformCompat);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -730,7 +746,8 @@ final class BroadcastRecord extends Binder {
}
/**
- * Determine if the result of {@link #calculateBlockedUntilBeyondCount(List, boolean)}
+ * Determine if the result of
+ * {@link #calculateBlockedUntilBeyondCount(List, boolean, PlatformCompat)}
* has prioritized tranches of receivers.
*/
@VisibleForTesting
@@ -754,37 +771,121 @@ final class BroadcastRecord extends Binder {
*/
@VisibleForTesting
static @NonNull int[] calculateBlockedUntilBeyondCount(
- @NonNull List<Object> receivers, boolean ordered) {
+ @NonNull List<Object> receivers, boolean ordered, PlatformCompat platformCompat) {
final int N = receivers.size();
final int[] blockedUntilBeyondCount = new int[N];
- int lastPriority = 0;
- int lastPriorityIndex = 0;
- for (int i = 0; i < N; i++) {
- if (ordered) {
- // When sending an ordered broadcast, we need to block this
- // receiver until all previous receivers have terminated
+ if (ordered) {
+ // When sending an ordered broadcast, we need to block this
+ // receiver until all previous receivers have terminated
+ for (int i = 0; i < N; i++) {
blockedUntilBeyondCount[i] = i;
+ }
+ } else {
+ if (Flags.limitPriorityScope()) {
+ final boolean[] changeEnabled = calculateChangeStateForReceivers(
+ receivers, CHANGE_LIMIT_PRIORITY_SCOPE, platformCompat);
+
+ // Priority of the previous tranche
+ int lastTranchePriority = 0;
+ // Priority of the current tranche
+ int currentTranchePriority = 0;
+ // Index of the last receiver in the previous tranche
+ int lastTranchePriorityIndex = -1;
+ // Index of the last receiver with change disabled in the previous tranche
+ int lastTrancheChangeDisabledIndex = -1;
+ // Index of the last receiver with change disabled in the current tranche
+ int currentTrancheChangeDisabledIndex = -1;
+
+ for (int i = 0; i < N; i++) {
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if (i == 0) {
+ currentTranchePriority = thisPriority;
+ if (!changeEnabled[i]) {
+ currentTrancheChangeDisabledIndex = i;
+ }
+ continue;
+ }
+
+ // Check if a new priority tranche has started
+ if (thisPriority != currentTranchePriority) {
+ // Update tranche boundaries and reset the disabled index.
+ if (currentTrancheChangeDisabledIndex != -1) {
+ lastTrancheChangeDisabledIndex = currentTrancheChangeDisabledIndex;
+ }
+ lastTranchePriority = currentTranchePriority;
+ lastTranchePriorityIndex = i - 1;
+ currentTranchePriority = thisPriority;
+ currentTrancheChangeDisabledIndex = -1;
+ }
+ if (!changeEnabled[i]) {
+ currentTrancheChangeDisabledIndex = i;
+
+ // Since the change is disabled, block the current receiver until the
+ // last receiver in the previous tranche.
+ blockedUntilBeyondCount[i] = lastTranchePriorityIndex + 1;
+ } else if (thisPriority != lastTranchePriority) {
+ // If the changeId was disabled for an earlier receiver and the current
+ // receiver has a different priority, block the current receiver
+ // until that earlier receiver.
+ if (lastTrancheChangeDisabledIndex != -1) {
+ blockedUntilBeyondCount[i] = lastTrancheChangeDisabledIndex + 1;
+ }
+ }
+ }
+ // If the entire list is in the same priority tranche or no receivers had
+ // changeId disabled, mark as -1 to indicate that none of them need to wait
+ if (N > 0 && (lastTranchePriorityIndex == -1
+ || (lastTrancheChangeDisabledIndex == -1
+ && currentTrancheChangeDisabledIndex == -1))) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
+ }
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
- final int thisPriority = getReceiverPriority(receivers.get(i));
- if ((i == 0) || (thisPriority != lastPriority)) {
- lastPriority = thisPriority;
- lastPriorityIndex = i;
- blockedUntilBeyondCount[i] = i;
- } else {
- blockedUntilBeyondCount[i] = lastPriorityIndex;
+ int lastPriority = 0;
+ int lastPriorityIndex = 0;
+ for (int i = 0; i < N; i++) {
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if ((i == 0) || (thisPriority != lastPriority)) {
+ lastPriority = thisPriority;
+ lastPriorityIndex = i;
+ blockedUntilBeyondCount[i] = i;
+ } else {
+ blockedUntilBeyondCount[i] = lastPriorityIndex;
+ }
+ }
+ // If the entire list is in the same priority tranche, mark as -1 to
+ // indicate that none of them need to wait
+ if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
}
}
}
- // If the entire list is in the same priority tranche, mark as -1 to
- // indicate that none of them need to wait
- if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
- Arrays.fill(blockedUntilBeyondCount, -1);
- }
return blockedUntilBeyondCount;
}
+ @VisibleForTesting
+ static @NonNull boolean[] calculateChangeStateForReceivers(@NonNull List<Object> receivers,
+ long changeId, PlatformCompat platformCompat) {
+ final SparseBooleanArray changeStateForUids = new SparseBooleanArray();
+ final int count = receivers.size();
+ final boolean[] changeStateForReceivers = new boolean[count];
+ for (int i = 0; i < count; ++i) {
+ final int receiverUid = getReceiverUid(receivers.get(i));
+ final boolean isChangeEnabled;
+ final int idx = changeStateForUids.indexOfKey(receiverUid);
+ if (idx >= 0) {
+ isChangeEnabled = changeStateForUids.valueAt(idx);
+ } else {
+ isChangeEnabled = platformCompat.isChangeEnabledByUidInternalNoLogging(
+ changeId, receiverUid);
+ changeStateForUids.put(receiverUid, isChangeEnabled);
+ }
+ changeStateForReceivers[i] = isChangeEnabled;
+ }
+ return changeStateForReceivers;
+ }
+
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
return ((BroadcastFilter) receiver).owningUid;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8dc7c7345f79..3dd5ec9a3834 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,6 +42,7 @@ import android.aconfigd.Aconfigd.StorageReturnMessages;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -144,7 +145,6 @@ public class SettingsToPropertiesMapper {
"android_core_networking",
"android_health_services",
"android_sdk",
- "android_stylus",
"aoc",
"app_widgets",
"arc_next",
@@ -209,6 +209,7 @@ public class SettingsToPropertiesMapper {
"pixel_continuity",
"pixel_perf",
"pixel_sensors",
+ "pixel_state_server",
"pixel_system_sw_video",
"pixel_video_sw",
"pixel_watch",
@@ -456,9 +457,11 @@ public class SettingsToPropertiesMapper {
static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
// connect to aconfigd socket
LocalSocket client = new LocalSocket();
+ String socketName = enableSystemAconfigdRust()
+ ? "aconfigd_system" : "aconfigd";
try{
client.connect(new LocalSocketAddress(
- "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+ socketName, LocalSocketAddress.Namespace.RESERVED));
Slog.d(TAG, "connected to aconfigd socket");
} catch (IOException ioe) {
logErr("failed to connect to aconfigd socket", ioe);
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
index b1185d552941..7f169db7dcec 100644
--- a/services/core/java/com/android/server/am/broadcasts_flags.aconfig
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -7,4 +7,12 @@ flag {
description: "Restrict priority values defined by non-system apps"
is_fixed_read_only: true
bug: "369487976"
+}
+
+flag {
+ name: "limit_priority_scope"
+ namespace: "backstage_power"
+ description: "Limit the scope of receiver priorities to within a process"
+ is_fixed_read_only: true
+ bug: "369487976"
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0cf55bb2bf17..6ba356990cac 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9193,16 +9193,6 @@ public class AudioService extends IAudioService.Stub
mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
- final int status = AudioSystem.initStreamVolume(
- streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
- if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.enqueue(new EventLogger.StringEvent(
- "VSS() stream:" + streamType + " initStreamVolume=" + status)
- .printLog(ALOGE, TAG));
- sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
- "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
- }
-
updateIndexFactors();
mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
@@ -9267,6 +9257,19 @@ public class AudioService extends IAudioService.Stub
mIndexMinNoPerm = mIndexMin;
}
}
+
+ final int status = AudioSystem.initStreamVolume(
+ mStreamType, mIndexMin / 10, mIndexMax / 10);
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "updateIndexFactors() stream:" + mStreamType + " index min/max:"
+ + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
+ + mIndexStepFactor).printSlog(ALOGI, TAG));
+ if (status != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "Failed initStreamVolume with status=" + status).printSlog(ALOGE, TAG));
+ sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+ "updateIndexFactors()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 00280c8f9c04..65780238ede4 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -288,11 +288,13 @@ public class BiometricService extends SystemService {
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public SettingObserver(Context context, Handler handler,
- List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
+ List<BiometricService.EnabledOnKeyguardCallback> callbacks,
+ UserManager userManager, FingerprintManager fingerprintManager,
+ FaceManager faceManager) {
super(handler);
mContentResolver = context.getContentResolver();
mCallbacks = callbacks;
- mUserManager = context.getSystemService(UserManager.class);
+ mUserManager = userManager;
final boolean hasFingerprint = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -304,7 +306,7 @@ public class BiometricService extends SystemService {
Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
&& hasFace && !hasFingerprint;
- addBiometricListenersForMandatoryBiometrics(context);
+ addBiometricListenersForMandatoryBiometrics(context, fingerprintManager, faceManager);
updateContentObserver();
}
@@ -431,11 +433,21 @@ public class BiometricService extends SystemService {
public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
+ Slog.d(TAG, "update mb toggle for user " + userId);
updateMandatoryBiometricsForAllProfiles(userId);
}
if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
+ Slog.d(TAG, "update mb reqs for user " + userId);
updateMandatoryBiometricsRequirementsForAllProfiles(userId);
}
+
+ Slog.d(TAG, mMandatoryBiometricsEnabled.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS)
+ + " " + mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
+ + " " + getEnabledForApps(userId)
+ + " " + (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+ || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)));
return mMandatoryBiometricsEnabled.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
@@ -456,11 +468,23 @@ public class BiometricService extends SystemService {
private void updateMandatoryBiometricsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsEnabled.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsEnabled.put(profileUserId,
+ Settings.Secure.getIntForUser(
+ mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsEnabled.put(userId,
Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
@@ -470,11 +494,24 @@ public class BiometricService extends SystemService {
private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
+ ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsRequirementsSatisfied.put(userId,
Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
@@ -482,10 +519,8 @@ public class BiometricService extends SystemService {
}
}
- private void addBiometricListenersForMandatoryBiometrics(Context context) {
- final FingerprintManager fingerprintManager = context.getSystemService(
- FingerprintManager.class);
- final FaceManager faceManager = context.getSystemService(FaceManager.class);
+ private void addBiometricListenersForMandatoryBiometrics(Context context,
+ FingerprintManager fingerprintManager, FaceManager faceManager) {
if (fingerprintManager != null) {
fingerprintManager.addAuthenticatorsRegisteredCallback(
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1169,7 +1204,9 @@ public class BiometricService extends SystemService {
*/
public SettingObserver getSettingObserver(Context context, Handler handler,
List<EnabledOnKeyguardCallback> callbacks) {
- return new SettingObserver(context, handler, callbacks);
+ return new SettingObserver(context, handler, callbacks, context.getSystemService(
+ UserManager.class), context.getSystemService(FingerprintManager.class),
+ context.getSystemService(FaceManager.class));
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 4ad7c10a1444..d2c044fdbb5e 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -255,6 +255,11 @@ final class DisplayDeviceInfo {
public static final int DIFF_MODE_ID = 1 << 7;
/**
+ * Diff result: The frame rate override list differs.
+ */
+ public static final int DIFF_FRAME_RATE_OVERRIDE = 1 << 8;
+
+ /**
* Diff result: Catch-all for "everything changed"
*/
public static final int DIFF_EVERYTHING = 0XFFFFFFFF;
@@ -523,6 +528,9 @@ final class DisplayDeviceInfo {
if (modeId != other.modeId) {
diff |= DIFF_MODE_ID;
}
+ if (!Arrays.equals(frameRateOverrides, other.frameRateOverrides)) {
+ diff |= DIFF_FRAME_RATE_OVERRIDE;
+ }
if (!Objects.equals(name, other.name)
|| !Objects.equals(uniqueId, other.uniqueId)
|| width != other.width
@@ -546,7 +554,6 @@ final class DisplayDeviceInfo {
|| !Objects.equals(deviceProductInfo, other.deviceProductInfo)
|| ownerUid != other.ownerUid
|| !Objects.equals(ownerPackageName, other.ownerPackageName)
- || !Arrays.equals(frameRateOverrides, other.frameRateOverrides)
|| !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
|| !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
|| !BrightnessSynchronizer.floatEquals(brightnessDefault,
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 086f8a94d9b8..5f7bc4effa1b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -27,6 +27,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.server.display.DisplayManagerService.SyncRoot;
import com.android.server.display.utils.DebugUtils;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -177,18 +178,22 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
"handleDisplayDeviceChanged");
}
int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
- if (diff == DisplayDeviceInfo.DIFF_STATE) {
+ if (diff == 0) {
+ Slog.i(TAG, "Display device same: " + info);
+ } else if (diff == DisplayDeviceInfo.DIFF_STATE) {
Slog.i(TAG, "Display device changed state: \"" + info.name
+ "\", " + Display.stateToString(info.state));
} else if (diff == DisplayDeviceInfo.DIFF_ROTATION) {
Slog.i(TAG, "Display device rotated: \"" + info.name
+ "\", " + Surface.rotationToString(info.rotation));
- } else if (diff
- == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) {
+ } else if ((diff &
+ (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS
+ | DisplayDeviceInfo.DIFF_FRAME_RATE_OVERRIDE)) != 0) {
Slog.i(TAG, "Display device changed render timings: \"" + info.name
+ "\", renderFrameRate=" + info.renderFrameRate
+ ", presentationDeadlineNanos=" + info.presentationDeadlineNanos
- + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos);
+ + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos
+ + ", frameRateOverrides=" + Arrays.toString(info.frameRateOverrides));
} else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) {
if (DEBUG) {
Slog.i(TAG, "Display device changed committed state: \"" + info.name
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0e77040187e1..5a2610b00772 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -96,6 +96,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -118,6 +119,7 @@ import android.os.IBinder.DeathRecipient;
import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -4321,6 +4323,10 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
final class BinderService extends IDisplayManager.Stub {
+ BinderService() {
+ super(PermissionEnforcer.fromContext(getContext()));
+ }
+
/**
* Returns information about the specified logical display.
*
@@ -5202,6 +5208,25 @@ public final class DisplayManagerService extends SystemService {
}
return ddc.getDefaultDozeBrightness();
}
+
+ @EnforcePermission(MANAGE_DISPLAYS)
+ @Override // Binder call
+ public DisplayTopology getDisplayTopology() {
+ getDisplayTopology_enforcePermission();
+ if (mDisplayTopologyCoordinator == null) {
+ return null;
+ }
+ return mDisplayTopologyCoordinator.getTopology();
+ }
+
+ @EnforcePermission(MANAGE_DISPLAYS)
+ @Override // Binder call
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopology_enforcePermission();
+ if (mDisplayTopologyCoordinator != null) {
+ mDisplayTopologyCoordinator.setTopology(topology);
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index b101e5893b97..47226861545f 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.hardware.display.DisplayTopology;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayInfo;
@@ -33,7 +34,7 @@ import java.util.function.BooleanSupplier;
class DisplayTopologyCoordinator {
@GuardedBy("mLock")
- private final DisplayTopology mTopology;
+ private DisplayTopology mTopology;
/**
* Check if extended displays are enabled. If not, a topology is not needed.
@@ -76,6 +77,21 @@ class DisplayTopologyCoordinator {
}
/**
+ * @return A deep copy of the topology.
+ */
+ DisplayTopology getTopology() {
+ synchronized (mLock) {
+ return mTopology;
+ }
+ }
+
+ void setTopology(DisplayTopology topology) {
+ synchronized (mLock) {
+ mTopology = topology;
+ }
+ }
+
+ /**
* Print the object's state and debug information into the given stream.
* @param pw The stream to dump information to.
*/
@@ -108,6 +124,7 @@ class DisplayTopologyCoordinator {
&& info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
}
+ @VisibleForTesting
static class Injector {
DisplayTopology getTopology() {
return new DisplayTopology();
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 794eb8754820..0c04be10d06d 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall;
import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -353,28 +354,32 @@ public final class DreamManagerService extends SystemService {
@Override
public void onUserStarting(@NonNull TargetUser user) {
super.onUserStarting(user);
- mHandler.post(() -> {
- final int userId = user.getUserIdentifier();
- if (!mPackageMonitors.contains(userId)) {
- final PackageMonitor monitor = new PerUserPackageMonitor();
- monitor.register(mContext, UserHandle.of(userId), mHandler);
- mPackageMonitors.put(userId, monitor);
- } else {
- Slog.w(TAG, "Package monitor already registered for " + userId);
- }
- });
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final int userId = user.getUserIdentifier();
+ if (!mPackageMonitors.contains(userId)) {
+ final PackageMonitor monitor = new PerUserPackageMonitor();
+ monitor.register(mContext, UserHandle.of(userId), mHandler);
+ mPackageMonitors.put(userId, monitor);
+ } else {
+ Slog.w(TAG, "Package monitor already registered for " + userId);
+ }
+ });
+ }
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
super.onUserStopping(user);
- mHandler.post(() -> {
- final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
- user.getUserIdentifier());
- if (monitor != null) {
- monitor.unregister();
- }
- });
+ if (cleanupDreamSettingsOnUninstall()) {
+ mHandler.post(() -> {
+ final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
+ user.getUserIdentifier());
+ if (monitor != null) {
+ monitor.unregister();
+ }
+ });
+ }
}
private void dumpInternal(PrintWriter pw) {
@@ -715,15 +720,23 @@ public final class DreamManagerService extends SystemService {
userId));
if (componentNames != null) {
// Filter out any components in the removed package.
- final ComponentName[] filteredComponents = Arrays.stream(componentNames).filter(
- (componentName -> !TextUtils.equals(componentName.getPackageName(),
- packageName))).toArray(ComponentName[]::new);
+ final ComponentName[] filteredComponents =
+ Arrays.stream(componentNames)
+ .filter((componentName -> !isSamePackage(packageName, componentName)))
+ .toArray(ComponentName[]::new);
if (filteredComponents.length != componentNames.length) {
setDreamComponentsForUser(userId, filteredComponents);
}
}
}
+ private static boolean isSamePackage(String packageName, ComponentName componentName) {
+ if (packageName == null || componentName == null) {
+ return false;
+ }
+ return TextUtils.equals(componentName.getPackageName(), packageName);
+ }
+
private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.SCREENSAVER_COMPONENTS,
@@ -884,7 +897,10 @@ public final class DreamManagerService extends SystemService {
}
StringBuilder names = new StringBuilder();
for (ComponentName componentName : componentNames) {
- if (names.length() > 0) {
+ if (componentName == null) {
+ continue;
+ }
+ if (!names.isEmpty()) {
names.append(',');
}
names.append(componentName.flattenToString());
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f2e2f653f929..5b4c0337862b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -1001,7 +1001,7 @@ final class HdmiCecController {
try {
// Create an AIDL callback that can callback onHotplugEvent
mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback));
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
}
}
@@ -1134,7 +1134,7 @@ final class HdmiCecController {
i++;
}
return hdmiPortInfo;
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Failed to get port information : ", e);
return null;
}
@@ -1144,7 +1144,7 @@ final class HdmiCecController {
public boolean nativeIsConnected(int port) {
try {
return mHdmiConnection.isConnected(port);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error("Failed to get connection info : ", e);
return false;
}
@@ -1158,7 +1158,7 @@ final class HdmiCecController {
HdmiLogger.error(
"Could not set HPD signal type for portId " + portId + " to " + signal
+ ". Error: ", sse.errorCode);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error(
"Could not set HPD signal type for portId " + portId + " to " + signal
+ ". Exception: ", e);
@@ -1169,7 +1169,7 @@ final class HdmiCecController {
public int nativeGetHpdSignalType(int portId) {
try {
return mHdmiConnection.getHpdSignal(portId);
- } catch (RemoteException e) {
+ } catch (RemoteException | NullPointerException e) {
HdmiLogger.error(
"Could not get HPD signal type for portId " + portId + ". Exception: ", e);
return Constants.HDMI_HPD_TYPE_PHYSICAL;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index bf415a344f4c..7505c710f483 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -646,9 +646,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
int address = message.getSource();
int type = message.getParams()[2];
- if (!ActiveSource.of(address, path).equals(getActiveSource())) {
- HdmiLogger.debug("Check if a new device is connected to the active path");
- handleNewDeviceAtTheTailOfActivePath(path);
+ if (getActiveSource().logicalAddress != address && getActivePath() == path) {
+ HdmiLogger.debug("New logical address detected on the current active path.");
+ startRoutingControl(path, path, null);
}
startNewDeviceAction(ActiveSource.of(address, path), type);
return Constants.HANDLED;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 132d6fa377eb..0c5069f81774 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -771,6 +771,14 @@ public class HdmiControlService extends SystemService {
Slog.i(TAG, "Device does not support eARC.");
}
mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
+ if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+ Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+ + " mode.");
+ getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+ HDMI_CEC_CONTROL_ENABLED);
+ setWasCecDisabledOnStandbyByLowEnergyMode(false);
+ setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
+ }
if (isCecControlEnabled()) {
initializeCec(INITIATED_BY_BOOT_UP);
} else {
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index e545dd507f28..2e7f5c083ac3 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -215,6 +215,12 @@ final class InputGestureManager {
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
}
if (keyboardA11yShortcutControl()) {
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 155ffe805519..0124e25f0655 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
@@ -750,6 +751,28 @@ final class KeyGestureController {
}
}
break;
+ case KeyEvent.KEYCODE_LOCK:
+ if (enableNew25q2Keycodes()) {
+ if (firstDown) {
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_LOCK},
+ /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+ /* flags = */0, /* appLaunchData = */null);
+ }
+ }
+ return true;
+ case KeyEvent.KEYCODE_FULLSCREEN:
+ if (enableNew25q2Keycodes()) {
+ if (firstDown) {
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN},
+ /* modifierState = */0,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+ /* flags = */0, /* appLaunchData = */null);
+ }
+ }
+ return true;
case KeyEvent.KEYCODE_ASSIST:
Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
return true;
@@ -764,6 +787,9 @@ final class KeyGestureController {
Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+ " interceptKeyBeforeQueueing");
return true;
+ case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+ // TODO(b/365920375): Implement 25Q2 keycode implementation in system
+ return true;
}
// Handle custom shortcuts
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index acc8f6634f5c..f611c57dab03 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -35,6 +35,7 @@ import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
import android.hardware.location.IContextHubCallback;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
@@ -57,6 +58,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.proto.ProtoOutputStream;
@@ -134,6 +136,9 @@ public class ContextHubService extends IContextHubService.Stub {
private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
private List<String> mSupportedContextHubPerms;
private List<ContextHubInfo> mContextHubInfoList;
+
+ @Nullable private final HubInfoRegistry mHubInfoRegistry;
+
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -309,10 +314,21 @@ public class ContextHubService extends IContextHubService.Stub {
mContext = context;
long startTimeNs = SystemClock.elapsedRealtimeNanos();
mContextHubWrapper = contextHubWrapper;
+
if (!initContextHubServiceState(startTimeNs)) {
Log.e(TAG, "Failed to initialize the Context Hub Service");
+ mHubInfoRegistry = null;
return;
}
+
+ if (Flags.offloadApi()) {
+ mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper);
+ Log.i(TAG, "Enabling generic offload API");
+ } else {
+ mHubInfoRegistry = null;
+ Log.i(TAG, "Disabling generic offload API");
+ }
+
initDefaultClientMap();
initLocationSettingNotifications();
@@ -427,7 +443,7 @@ public class ContextHubService extends IContextHubService.Stub {
Pair<List<ContextHubInfo>, List<String>> hubInfo;
try {
- hubInfo = mContextHubWrapper.getHubs();
+ hubInfo = mContextHubWrapper.getContextHubs();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList());
@@ -713,6 +729,16 @@ public class ContextHubService extends IContextHubService.Stub {
return mContextHubInfoList;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public List<HubInfo> getHubs() throws RemoteException {
+ super.getHubs_enforcePermission();
+ if (mHubInfoRegistry == null) {
+ return Collections.emptyList();
+ }
+ return mHubInfoRegistry.getHubs();
+ }
+
/**
* Creates an internal load transaction callback to be used for old API clients
*
@@ -1417,6 +1443,8 @@ public class ContextHubService extends IContextHubService.Stub {
}
}
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ pw = ipw;
pw.println("Dumping ContextHub Service");
pw.println("");
@@ -1428,6 +1456,11 @@ public class ContextHubService extends IContextHubService.Stub {
pw.println("Supported permissions: "
+ Arrays.toString(mSupportedContextHubPerms.toArray()));
pw.println("");
+
+ if (mHubInfoRegistry != null) {
+ mHubInfoRegistry.dump(ipw);
+ }
+
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
new file mode 100644
index 000000000000..68de9dbda2e1
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -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.server.location.contexthub;
+
+import android.hardware.location.HubInfo;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+class HubInfoRegistry {
+ private static final String TAG = "HubInfoRegistry";
+
+ private final IContextHubWrapper mContextHubWrapper;
+
+ private final List<HubInfo> mHubsInfo;
+
+ HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
+ List<HubInfo> hubInfos;
+ mContextHubWrapper = contextHubWrapper;
+ try {
+ hubInfos = mContextHubWrapper.getHubs();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while getting Hub info", e);
+ hubInfos = Collections.emptyList();
+ }
+ mHubsInfo = hubInfos;
+ }
+
+ /** Retrieve the list of hubs available. */
+ List<HubInfo> getHubs() {
+ return mHubsInfo;
+ }
+
+ void dump(IndentingPrintWriter ipw) {
+ ipw.println(TAG);
+
+ ipw.increaseIndent();
+ for (HubInfo hubInfo : mHubsInfo) {
+ ipw.println(hubInfo);
+ }
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 5e9277ac0faf..6656a6fe9eb4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -30,9 +30,11 @@ import android.hardware.contexthub.V1_2.HubAppInfo;
import android.hardware.contexthub.V1_2.IContexthubCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
+import android.hardware.location.VendorHubInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -52,13 +54,14 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @hide
*/
public abstract class IContextHubWrapper {
+ private static final boolean DEBUG = false;
private static final String TAG = "IContextHubWrapper";
/**
@@ -217,10 +220,14 @@ public abstract class IContextHubWrapper {
return proxy == null ? null : new ContextHubWrapperAidl(proxy);
}
- /**
- * Calls the appropriate getHubs function depending on the HAL version.
- */
- public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException;
+ /** Calls the appropriate getHubs function depending on the HAL version. */
+ public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs()
+ throws RemoteException;
+
+ /** Calls the appropriate getHubs function depending on the HAL version. */
+ public List<HubInfo> getHubs() throws RemoteException {
+ return Collections.emptyList();
+ }
/**
* @return True if this version of the Contexthub HAL supports Location setting notifications.
@@ -556,7 +563,7 @@ public abstract class IContextHubWrapper {
mIsTestModeEnabled.set(false);
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
@@ -574,6 +581,47 @@ public abstract class IContextHubWrapper {
return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions));
}
+ public List<HubInfo> getHubs() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return Collections.emptyList();
+ }
+
+ List<HubInfo> retVal = new ArrayList<>();
+ final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs();
+
+ for (android.hardware.contexthub.HubInfo halHub : halHubs) {
+ /* HAL -> API Type conversion */
+ final HubInfo hubInfo;
+ switch (halHub.hubDetails.getTag()) {
+ case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo:
+ ContextHubInfo contextHubInfo =
+ new ContextHubInfo(halHub.hubDetails.getContextHubInfo());
+ hubInfo = new HubInfo(halHub.hubId, contextHubInfo);
+ break;
+ case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo:
+ VendorHubInfo vendorHubInfo =
+ new VendorHubInfo(halHub.hubDetails.getVendorHubInfo());
+ hubInfo = new HubInfo(halHub.hubId, vendorHubInfo);
+ break;
+ default:
+ Log.w(TAG, "getHubs: invalid hub: " + halHub);
+ // Invalid
+ continue;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getHubs: hubInfo=" + hubInfo);
+ }
+ retVal.add(hubInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "getHubs: total count=" + retVal.size());
+ }
+ return retVal;
+ }
+
public boolean supportsLocationSettingNotifications() {
return true;
}
@@ -1061,7 +1109,7 @@ public abstract class IContextHubWrapper {
mHub = hub;
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
for (ContextHub hub : mHub.getHubs()) {
hubInfoList.add(new ContextHubInfo(hub));
@@ -1106,7 +1154,7 @@ public abstract class IContextHubWrapper {
mHub = hub;
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
for (ContextHub hub : mHub.getHubs()) {
hubInfoList.add(new ContextHubInfo(hub));
@@ -1170,7 +1218,7 @@ public abstract class IContextHubWrapper {
mHubInfo = new Pair(hubInfoList, supportedPermissions);
}
- public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
mHub.getHubs_1_2(this);
return mHubInfo;
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index a45ea1d8369d..21ae1820f6f7 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -59,15 +59,15 @@ public class MediaQualityService extends SystemService {
return pp;
}
@Override
- public void updatePictureProfile(long id, PictureProfile pp) {
+ public void updatePictureProfile(String id, PictureProfile pp) {
// TODO: implement
}
@Override
- public void removePictureProfile(long id) {
+ public void removePictureProfile(String id) {
// TODO: implement
}
@Override
- public PictureProfile getPictureProfileById(long id) {
+ public PictureProfile getPictureProfile(int type, String name) {
return null;
}
@Override
@@ -79,7 +79,7 @@ public class MediaQualityService extends SystemService {
return new ArrayList<>();
}
@Override
- public List<PictureProfile> getAllPictureProfiles() {
+ public List<String> getPictureProfilePackageNames() {
return new ArrayList<>();
}
@@ -89,15 +89,15 @@ public class MediaQualityService extends SystemService {
return pp;
}
@Override
- public void updateSoundProfile(long id, SoundProfile pp) {
+ public void updateSoundProfile(String id, SoundProfile pp) {
// TODO: implement
}
@Override
- public void removeSoundProfile(long id) {
+ public void removeSoundProfile(String id) {
// TODO: implement
}
@Override
- public SoundProfile getSoundProfileById(long id) {
+ public SoundProfile getSoundProfileById(String id) {
return null;
}
@Override
@@ -109,7 +109,7 @@ public class MediaQualityService extends SystemService {
return new ArrayList<>();
}
@Override
- public List<SoundProfile> getAllSoundProfiles() {
+ public List<String> getSoundProfilePackageNames() {
return new ArrayList<>();
}
@@ -138,6 +138,14 @@ public class MediaQualityService extends SystemService {
return new ArrayList<>();
}
+ @Override
+ public List<String> getPictureProfileAllowList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setPictureProfileAllowList(List<String> packages) {
+ }
@Override
public boolean isSupported() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2a3be1e119bf..7de2815eba6b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -513,12 +513,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private boolean mLoadedRestrictBackground;
/**
- * Whether or not network for apps in proc-states greater than
- * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
- */
- private boolean mBackgroundNetworkRestricted;
-
- /**
* Whether or not metered firewall chains should be used for uid policy controlling access to
* metered networks.
*/
@@ -1117,14 +1111,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
writePolicyAL();
}
- // The flag is boot-stable.
- mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
- if (mBackgroundNetworkRestricted) {
- // Firewall rules and UidBlockedState will get updated in
- // updateRulesForGlobalChangeAL below.
- enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
- }
-
+ enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -1135,11 +1122,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
| ActivityManager.UID_OBSERVER_GONE
| ActivityManager.UID_OBSERVER_CAPABILITY;
-
- final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
- : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
- cutpoint, "android");
+ PROCESS_STATE_UNKNOWN, "android");
mNetworkManager.registerObserver(mAlertObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
@@ -1280,21 +1264,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// different chains may change.
return true;
}
- if (mBackgroundNetworkRestricted) {
- if ((previousProcState >= BACKGROUND_THRESHOLD_STATE)
+ if ((previousProcState >= BACKGROUND_THRESHOLD_STATE)
!= (newProcState >= BACKGROUND_THRESHOLD_STATE)) {
- // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will
- // need to be re-evaluated for the background chain.
- return true;
- }
- if (mUseDifferentDelaysForBackgroundChain
- && newProcState >= BACKGROUND_THRESHOLD_STATE
- && getBackgroundTransitioningDelay(newProcState)
- < getBackgroundTransitioningDelay(previousProcState)) {
- // The old and new proc-state both are in the blocked state but the background
- // transition delay is reduced, so we may have to update the rules sooner.
- return true;
- }
+ // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will
+ // need to be re-evaluated for the background chain.
+ return true;
+ }
+ if (mUseDifferentDelaysForBackgroundChain
+ && newProcState >= BACKGROUND_THRESHOLD_STATE
+ && getBackgroundTransitioningDelay(newProcState)
+ < getBackgroundTransitioningDelay(previousProcState)) {
+ // The old and new proc-state both are in the blocked state but the background
+ // transition delay is reduced, so we may have to update the rules sooner.
+ return true;
}
final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
| PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
@@ -1367,9 +1349,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
synchronized (mUidRulesFirstLock) {
updatePowerSaveAllowlistUL();
- if (mBackgroundNetworkRestricted) {
- updateRulesForBackgroundChainUL();
- }
+ updateRulesForBackgroundChainUL();
updateRulesForRestrictPowerUL();
updateRulesForAppIdleUL();
}
@@ -4100,8 +4080,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.println();
fout.println("Flags:");
- fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": "
- + mBackgroundNetworkRestricted);
fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": "
+ mUseMeteredFirewallChains);
fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": "
@@ -4251,35 +4229,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.decreaseIndent();
}
- if (mBackgroundNetworkRestricted) {
+ fout.println();
+ if (mUseDifferentDelaysForBackgroundChain) {
+ fout.print("Background restrictions short delay: ");
+ TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout);
fout.println();
- if (mUseDifferentDelaysForBackgroundChain) {
- fout.print("Background restrictions short delay: ");
- TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout);
- fout.println();
- fout.print("Background restrictions long delay: ");
- TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout);
- fout.println();
- }
+ fout.print("Background restrictions long delay: ");
+ TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout);
+ fout.println();
+ }
- size = mBackgroundTransitioningUids.size();
- if (size > 0) {
- final long nowUptime = SystemClock.uptimeMillis();
- fout.println("Uids transitioning to background:");
- fout.increaseIndent();
- for (int i = 0; i < size; i++) {
- fout.print("UID=");
- fout.print(mBackgroundTransitioningUids.keyAt(i));
- fout.print(", ");
- TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i),
- nowUptime, fout);
- fout.println();
- }
- fout.decreaseIndent();
+ size = mBackgroundTransitioningUids.size();
+ if (size > 0) {
+ final long nowUptime = SystemClock.uptimeMillis();
+ fout.println("Uids transitioning to background:");
+ fout.increaseIndent();
+ for (int i = 0; i < size; i++) {
+ fout.print("UID=");
+ fout.print(mBackgroundTransitioningUids.keyAt(i));
+ fout.print(", ");
+ TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i),
+ nowUptime, fout);
+ fout.println();
}
- fout.println();
+ fout.decreaseIndent();
}
+ fout.println();
final SparseBooleanArray knownUids = new SparseBooleanArray();
collectKeys(mUidState, knownUids);
@@ -4465,51 +4441,49 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
updatePowerRestrictionRules = true;
}
- if (mBackgroundNetworkRestricted) {
- final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
- oldUidState);
- final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
- if (!wasAllowed && isAllowed) {
- mBackgroundTransitioningUids.delete(uid);
- updateRuleForBackgroundUL(uid);
- updatePowerRestrictionRules = true;
- } else if (!isAllowed) {
- final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid);
- final long completionTimeMs = SystemClock.uptimeMillis()
- + getBackgroundTransitioningDelay(procState);
- boolean completionTimeUpdated = false;
- if (wasAllowed) {
- // Rules need to transition from allowed to blocked after the respective
- // delay.
- if (transitionIdx < 0) {
- // This is just a defensive check in case the upstream code ever
- // makes multiple calls for the same process state change.
- mBackgroundTransitioningUids.put(uid, completionTimeMs);
- completionTimeUpdated = true;
- }
- } else if (mUseDifferentDelaysForBackgroundChain) {
- // wasAllowed was false, but the transition delay may have reduced.
- // Currently, this can happen when the uid transitions from
- // LAST_ACTIVITY to CACHED_ACTIVITY, for example.
- if (transitionIdx >= 0
- && completionTimeMs < mBackgroundTransitioningUids.valueAt(
- transitionIdx)) {
- mBackgroundTransitioningUids.setValueAt(transitionIdx,
- completionTimeMs);
- completionTimeUpdated = true;
- }
+ final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+ oldUidState);
+ final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+ if (!wasAllowed && isAllowed) {
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
+ updatePowerRestrictionRules = true;
+ } else if (!isAllowed) {
+ final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid);
+ final long completionTimeMs = SystemClock.uptimeMillis()
+ + getBackgroundTransitioningDelay(procState);
+ boolean completionTimeUpdated = false;
+ if (wasAllowed) {
+ // Rules need to transition from allowed to blocked after the respective
+ // delay.
+ if (transitionIdx < 0) {
+ // This is just a defensive check in case the upstream code ever
+ // makes multiple calls for the same process state change.
+ mBackgroundTransitioningUids.put(uid, completionTimeMs);
+ completionTimeUpdated = true;
}
- if (completionTimeUpdated
- && completionTimeMs < mNextProcessBackgroundUidsTime) {
- // Many uids may be in this "transitioning" state at the same time,
- // so we always keep one message to process transition completion at
- // the earliest time.
- mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS);
- mHandler.sendEmptyMessageAtTime(
- MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
- mNextProcessBackgroundUidsTime = completionTimeMs;
+ } else if (mUseDifferentDelaysForBackgroundChain) {
+ // wasAllowed was false, but the transition delay may have reduced.
+ // Currently, this can happen when the uid transitions from
+ // LAST_ACTIVITY to CACHED_ACTIVITY, for example.
+ if (transitionIdx >= 0
+ && completionTimeMs < mBackgroundTransitioningUids.valueAt(
+ transitionIdx)) {
+ mBackgroundTransitioningUids.setValueAt(transitionIdx,
+ completionTimeMs);
+ completionTimeUpdated = true;
}
}
+ if (completionTimeUpdated
+ && completionTimeMs < mNextProcessBackgroundUidsTime) {
+ // Many uids may be in this "transitioning" state at the same time,
+ // so we always keep one message to process transition completion at
+ // the earliest time.
+ mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS);
+ mHandler.sendEmptyMessageAtTime(
+ MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+ mNextProcessBackgroundUidsTime = completionTimeMs;
+ }
}
if (mLowPowerStandbyActive) {
boolean allowedInLpsChanged =
@@ -4545,12 +4519,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
if (mRestrictPower) {
updateRuleForRestrictPowerUL(uid);
}
- if (mBackgroundNetworkRestricted) {
- // Uid is no longer running, there is no point in any grace period of network
- // access during transitions to lower importance proc-states.
- mBackgroundTransitioningUids.delete(uid);
- updateRuleForBackgroundUL(uid);
- }
+ // Uid is no longer running, there is no point in any grace period of network
+ // access during transitions to lower importance proc-states.
+ mBackgroundTransitioningUids.delete(uid);
+ updateRuleForBackgroundUL(uid);
updateRulesForPowerRestrictionsUL(uid);
if (mLowPowerStandbyActive) {
updateRuleForLowPowerStandbyUL(uid);
@@ -5021,9 +4993,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
"updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
}
try {
- if (mBackgroundNetworkRestricted) {
- updateRulesForBackgroundChainUL();
- }
+ updateRulesForBackgroundChainUL();
updateRulesForAppIdleUL();
updateRulesForRestrictPowerUL();
updateRulesForRestrictBackgroundUL();
@@ -5183,9 +5153,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForDeviceIdleUL(uid);
updateRuleForRestrictPowerUL(uid);
- if (mBackgroundNetworkRestricted) {
- updateRuleForBackgroundUL(uid);
- }
+ updateRuleForBackgroundUL(uid);
// Update internal rules.
updateRulesForPowerRestrictionsUL(uid);
}
@@ -5358,9 +5326,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
updateRuleForDeviceIdleUL(uid);
updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
updateRuleForRestrictPowerUL(uid);
- if (mBackgroundNetworkRestricted) {
- updateRuleForBackgroundUL(uid);
- }
+ updateRuleForBackgroundUL(uid);
// If the uid has the necessary permissions, then it should be added to the restricted mode
// firewall allowlist.
@@ -5611,7 +5577,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
- newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
+ newBlockedReasons |= BLOCKED_REASON_APP_BACKGROUND;
newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5624,8 +5590,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
& ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
- newAllowedReasons |= (mBackgroundNetworkRestricted
- && isUidExemptFromBackgroundRestrictions(uid))
+ newAllowedReasons |= isUidExemptFromBackgroundRestrictions(uid)
? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 7f04e665567e..3c0ff6115fcd 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -2,13 +2,6 @@ package: "com.android.server.net"
container: "system"
flag {
- name: "network_blocked_for_top_sleeping_and_above"
- namespace: "backstage_power"
- description: "Block network access for apps in a low importance background state"
- bug: "304347838"
-}
-
-flag {
name: "use_metered_firewall_chains"
namespace: "backstage_power"
description: "Use metered firewall chains to control access to metered networks"
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 5febd5c07e3a..5914dbe44b0b 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -788,6 +788,20 @@ public class GroupHelper {
return;
}
+ // Check if summary & child notifications are not part of the same section/bundle
+ // Needs a check here if notification was bundled while enqueued
+ if (notificationRegroupOnClassification()
+ && android.service.notification.Flags.notificationClassification()) {
+ if (isGroupChildBundled(record, summaryByGroupKey)) {
+ if (DEBUG) {
+ Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
+ }
+ moveNotificationsToNewSection(record.getUserId(), pkgName,
+ List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)));
+ return;
+ }
+ }
+
// scenario 3: sparse/singleton groups
if (Flags.notificationForceGroupSingletons()) {
try {
@@ -800,6 +814,27 @@ public class GroupHelper {
}
}
+ private static boolean isGroupChildBundled(final NotificationRecord record,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String groupKey = record.getSbn().getGroupKey();
+
+ if (!sbn.isAppGroup()) {
+ return false;
+ }
+
+ if (record.getNotification().isGroupSummary()) {
+ return false;
+ }
+
+ final NotificationRecord summary = summaryByGroupKey.get(groupKey);
+ if (summary == null) {
+ return false;
+ }
+
+ return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId());
+ }
+
/**
* Called when a notification is removed, so that this helper can adjust the aggregate groups:
* - Removes the autogroup summary of the notification's section
@@ -1598,7 +1633,7 @@ public class GroupHelper {
final int mSummaryId;
private final Predicate<NotificationRecord> mSectionChecker;
- public NotificationSectioner(String name, int summaryId,
+ private NotificationSectioner(String name, int summaryId,
Predicate<NotificationRecord> sectionChecker) {
mName = name;
mSummaryId = summaryId;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 93482e769a2b..b0ef80793cd7 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -75,9 +75,7 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerService.DumpFilter;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
@@ -136,7 +134,6 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
protected final IPackageManager mPm;
protected final UserManager mUm;
- private final UserManagerInternal mUserManagerInternal;
private final Config mConfig;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -198,7 +195,6 @@ abstract public class ManagedServices {
mConfig = getConfig();
mApprovalLevel = APPROVAL_BY_COMPONENT;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
abstract protected Config getConfig();
@@ -1389,14 +1385,9 @@ abstract public class ManagedServices {
@GuardedBy("mMutex")
protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
final IntArray activeUsers,
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser,
- boolean isVisibleBackgroundUser) {
- // When it is a visible background user in Automotive MUMD environment,
- // don't clear mEnabledServicesForCurrentProfile and mEnabledServicesPackageNames.
- if (!isVisibleBackgroundUser) {
- mEnabledServicesForCurrentProfiles.clear();
- mEnabledServicesPackageNames.clear();
- }
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
+ mEnabledServicesForCurrentProfiles.clear();
+ mEnabledServicesPackageNames.clear();
final int nUserIds = activeUsers.size();
for (int i = 0; i < nUserIds; ++i) {
@@ -1417,12 +1408,7 @@ abstract public class ManagedServices {
}
componentsToBind.put(userId, add);
- // When it is a visible background user in Automotive MUMD environment,
- // skip adding items to mEnabledServicesForCurrentProfile
- // and mEnabledServicesPackageNames.
- if (isVisibleBackgroundUser) {
- continue;
- }
+
mEnabledServicesForCurrentProfiles.addAll(userComponents);
for (int j = 0; j < userComponents.size(); j++) {
@@ -1470,10 +1456,7 @@ abstract public class ManagedServices {
IntArray userIds = mUserProfiles.getCurrentProfileIds();
boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
- boolean isVisibleBackgroundUser = false;
if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
- isVisibleBackgroundUser =
- mUserManagerInternal.isVisibleBackgroundFullUser(userToRebind);
userIds = new IntArray(1);
userIds.add(userToRebind);
}
@@ -1488,8 +1471,7 @@ abstract public class ManagedServices {
// Filter approvedComponentsByUser to collect all of the components that are allowed
// for the currently active user(s).
- populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser,
- isVisibleBackgroundUser);
+ populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser);
// For every current non-system connection, disconnect services that are no longer
// approved, or ALL services if we are force rebinding
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
new file mode 100644
index 000000000000..8ec716077f46
--- /dev/null
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -0,0 +1,135 @@
+/*
+ * 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.os.instrumentation;
+
+import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION;
+import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PermissionManuallyEnforced;
+import android.content.Context;
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IDynamicInstrumentationManager;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import dalvik.system.VMDebug;
+
+import java.lang.reflect.Method;
+
+/**
+ * System private implementation of the {@link IDynamicInstrumentationManager interface}.
+ */
+public class DynamicInstrumentationManagerService extends SystemService {
+ public DynamicInstrumentationManagerService(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService());
+ }
+
+ private final class BinderService extends IDynamicInstrumentationManager.Stub {
+ @Override
+ @PermissionManuallyEnforced
+ public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+ @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) {
+ if (!com.android.art.flags.Flags.executableMethodFileOffsets()) {
+ throw new UnsupportedOperationException();
+ }
+ getContext().enforceCallingOrSelfPermission(
+ DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission");
+
+ if (targetProcess.processName == null
+ || !targetProcess.processName.equals("system_server")) {
+ throw new UnsupportedOperationException(
+ "system_server is the only supported target process");
+ }
+
+ Method method = parseMethodDescriptor(
+ getClass().getClassLoader(), methodDescriptor);
+ VMDebug.ExecutableMethodFileOffsets location =
+ VMDebug.getExecutableMethodFileOffsets(method);
+
+ if (location == null) {
+ return null;
+ }
+
+ ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+ ret.containerPath = location.getContainerPath();
+ ret.containerOffset = location.getContainerOffset();
+ ret.methodOffset = location.getMethodOffset();
+ return ret;
+ }
+ }
+
+ @VisibleForTesting
+ static Method parseMethodDescriptor(ClassLoader classLoader,
+ @NonNull MethodDescriptor descriptor) {
+ try {
+ Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
+ Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length];
+ for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) {
+ String typeName = descriptor.fullyQualifiedParameters[i];
+ boolean isArrayType = typeName.endsWith("[]");
+ if (isArrayType) {
+ typeName = typeName.substring(0, typeName.length() - 2);
+ }
+ switch (typeName) {
+ case "boolean":
+ parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class;
+ break;
+ case "byte":
+ parameters[i] = isArrayType ? byte.class.arrayType() : byte.class;
+ break;
+ case "char":
+ parameters[i] = isArrayType ? char.class.arrayType() : char.class;
+ break;
+ case "short":
+ parameters[i] = isArrayType ? short.class.arrayType() : short.class;
+ break;
+ case "int":
+ parameters[i] = isArrayType ? int.class.arrayType() : int.class;
+ break;
+ case "long":
+ parameters[i] = isArrayType ? long.class.arrayType() : long.class;
+ break;
+ case "float":
+ parameters[i] = isArrayType ? float.class.arrayType() : float.class;
+ break;
+ case "double":
+ parameters[i] = isArrayType ? double.class.arrayType() : double.class;
+ break;
+ default:
+ parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType()
+ : classLoader.loadClass(typeName);
+ }
+ }
+
+ return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "The specified method cannot be found. Is this descriptor valid? "
+ + descriptor, e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index af2bb17fd0e6..d538bb876b64 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -265,6 +265,7 @@ public class BackgroundInstallControlService extends SystemService {
@Override
public void handleMessage(Message msg) {
+ Slog.d(TAG, "Package event received: " + msg.what);
switch (msg.what) {
case MSG_USAGE_EVENT_RECEIVED:
mService.handleUsageEvent(
@@ -326,6 +327,8 @@ public class BackgroundInstallControlService extends SystemService {
return;
}
+ Slog.d(TAG, "handlePackageAdd: adding " + packageName + " from "
+ + userId + " and notifying callbacks");
initBackgroundInstalledPackages();
mBackgroundInstalledPackages.add(userId, packageName);
mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_INSTALL);
@@ -364,7 +367,11 @@ public class BackgroundInstallControlService extends SystemService {
// ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
// addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
- return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
+ if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
+ Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping");
+ return true;
+ }
+ return false;
}
private boolean wasForegroundInstallation(
@@ -407,6 +414,7 @@ public class BackgroundInstallControlService extends SystemService {
if (mBackgroundInstalledPackages.contains(userId, packageName)) {
mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
}
+ Slog.d(TAG, "handlePackageRemove: removing " + packageName + " from " + userId);
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 19406b46f5c0..8d039f19db8c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -83,7 +83,7 @@ import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
-import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
@@ -3993,10 +3993,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
case KeyEvent.KEYCODE_SCREENSHOT:
- if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+ if (firstDown) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
return true;
+ case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+ case KeyEvent.KEYCODE_LOCK:
+ case KeyEvent.KEYCODE_FULLSCREEN:
+ return true;
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -5666,9 +5670,23 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_MACRO_4:
result &= ~ACTION_PASS_TO_USER;
break;
- case KeyEvent.KEYCODE_EMOJI_PICKER:
- if (!emojiAndScreenshotKeycodesAvailable()) {
- // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+ case KeyEvent.KEYCODE_DICTATE:
+ case KeyEvent.KEYCODE_NEW:
+ case KeyEvent.KEYCODE_CLOSE:
+ case KeyEvent.KEYCODE_PRINT:
+ case KeyEvent.KEYCODE_F13:
+ case KeyEvent.KEYCODE_F14:
+ case KeyEvent.KEYCODE_F15:
+ case KeyEvent.KEYCODE_F16:
+ case KeyEvent.KEYCODE_F17:
+ case KeyEvent.KEYCODE_F18:
+ case KeyEvent.KEYCODE_F19:
+ case KeyEvent.KEYCODE_F20:
+ case KeyEvent.KEYCODE_F21:
+ case KeyEvent.KEYCODE_F22:
+ case KeyEvent.KEYCODE_F23:
+ case KeyEvent.KEYCODE_F24:
+ if (!enableNew25q2Keycodes()) {
result &= ~ACTION_PASS_TO_USER;
}
break;
@@ -5841,8 +5859,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5856,8 +5874,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -6205,7 +6223,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index af1ad13f1d15..04dbd1fea5d6 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,6 +25,7 @@ import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.view.KeyEvent.KEYCODE_POWER;
import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
import android.annotation.Nullable;
import android.content.Context;
@@ -107,13 +108,14 @@ class WindowWakeUpPolicy {
/**
* Wakes up from a key event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
* @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
+ boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
@@ -126,22 +128,31 @@ class WindowWakeUpPolicy {
&& mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
return true;
}
- wakeUp(
- eventTime,
- keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
- keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(
+ displayId,
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ } else {
+ wakeUp(
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ }
return true;
}
/**
* Wakes up from a motion event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
return false;
@@ -150,7 +161,11 @@ class WindowWakeUpPolicy {
&& mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
return true;
}
- wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ } else {
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ }
return true;
}
@@ -237,4 +252,12 @@ class WindowWakeUpPolicy {
private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
}
+
+ /** Wakes up given display. */
+ private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+ // If we're given an invalid display id to wake, fall back to waking default display
+ final int displayIdToWake =
+ displayId == Display.INVALID_DISPLAY ? Display.DEFAULT_DISPLAY : displayId;
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+ }
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 677a2dea665b..028ac57fc5a3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -303,6 +303,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+ private boolean mMoveWscLoggingToNotifierEnabled = false;
+
private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
@@ -5155,7 +5157,7 @@ public class BatteryStatsImpl extends BatteryStats {
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
- if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+ if (!mMoveWscLoggingToNotifierEnabled) {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, true /* acquired */,
getPowerManagerWakeLockLevel(type));
@@ -5206,7 +5208,7 @@ public class BatteryStatsImpl extends BatteryStats {
Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
- if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+ if (!mMoveWscLoggingToNotifierEnabled) {
mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
uidStats.mProcessState, false/* acquired */,
getPowerManagerWakeLockLevel(type));
@@ -15975,6 +15977,15 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ /**
+ * Controls where the logging of the WakelockStateChanged atom occurs:
+ * true = Notifier, false = BatteryStatsImpl.
+ */
+ public void setMoveWscLoggingToNotifierEnabled(boolean enabled) {
+ synchronized (this) {
+ mMoveWscLoggingToNotifierEnabled = enabled;
+ }
+ }
@GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
index 9398c7a854d4..b129fdc1b6e3 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
@@ -55,8 +55,8 @@ import java.util.Objects;
/**
* @hide
*/
-public class AdaptiveAuthService extends SystemService {
- private static final String TAG = "AdaptiveAuthService";
+public class AdaptiveAuthenticationService extends SystemService {
+ private static final String TAG = "AdaptiveAuthenticationService";
private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting
@@ -78,12 +78,12 @@ public class AdaptiveAuthService extends SystemService {
final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
- public AdaptiveAuthService(Context context) {
+ public AdaptiveAuthenticationService(Context context) {
this(context, new LockPatternUtils(context));
}
@VisibleForTesting
- public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) {
+ public AdaptiveAuthenticationService(Context context, LockPatternUtils lockPatternUtils) {
super(context);
mLockPatternUtils = lockPatternUtils;
mLockSettings = Objects.requireNonNull(
diff --git a/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 000000000000..29affcdb81aa
--- /dev/null
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1,3 @@
+hainingc@google.com
+jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index df44e50d2839..a92ac679b0f4 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -45,6 +45,7 @@ final class ExternalVibrationSession extends Vibration
void onExternalVibrationReleased(long vibrationId);
}
+ private final long mSessionId = VibrationSession.nextSessionId();
private final ExternalVibration mExternalVibration;
private final ExternalVibrationScale mScale = new ExternalVibrationScale();
private final VibratorManagerHooks mManagerHooks;
@@ -65,6 +66,11 @@ final class ExternalVibrationSession extends Vibration
}
@Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
public long getCreateUptimeMillis() {
return stats.getCreateUptimeMillis();
}
@@ -148,7 +154,12 @@ final class ExternalVibrationSession extends Vibration
@Override
public void notifySyncedVibratorsCallback(long vibrationId) {
- // ignored, external control does not expect callbacks from the vibrator manager
+ // ignored, external control does not expect callbacks from the vibrator manager for sync
+ }
+
+ @Override
+ public void notifySessionCallback() {
+ // ignored, external control does not expect callbacks from the vibrator manager for session
}
boolean isHoldingSameVibration(ExternalVibration vib) {
@@ -174,7 +185,8 @@ final class ExternalVibrationSession extends Vibration
@Override
public String toString() {
return "ExternalVibrationSession{"
- + "id=" + id
+ + "sessionId=" + mSessionId
+ + ", vibrationId=" + id
+ ", callerInfo=" + callerInfo
+ ", externalVibration=" + mExternalVibration
+ ", scale=" + mScale
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
index 67ba25f6b0b9..628221b09d77 100644
--- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -35,6 +35,7 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec
private static final String TAG = "SingleVibrationSession";
private final Object mLock = new Object();
+ private final long mSessionId = VibrationSession.nextSessionId();
private final IBinder mCallerToken;
private final HalVibration mVibration;
@@ -58,6 +59,11 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec
}
@Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
public long getCreateUptimeMillis() {
return mVibration.stats.getCreateUptimeMillis();
}
@@ -155,9 +161,15 @@ final class SingleVibrationSession implements VibrationSession, IBinder.DeathRec
}
@Override
+ public void notifySessionCallback() {
+ // ignored, external control does not expect callbacks from the vibrator manager for session
+ }
+
+ @Override
public String toString() {
return "SingleVibrationSession{"
- + "callerToken= " + mCallerToken
+ + "sessionId= " + mSessionId
+ + ", callerToken= " + mCallerToken
+ ", vibration=" + mVibration
+ '}';
}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
new file mode 100644
index 000000000000..07478e360d27
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.os.CancellationSignal;
+import android.os.CombinedVibration;
+import android.os.ExternalVibration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.VibrationAttributes;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session started by a vendor request that can trigger {@link CombinedVibration}.
+ */
+final class VendorVibrationSession extends IVibrationSession.Stub
+ implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
+ private static final String TAG = "VendorVibrationSession";
+
+ /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
+ interface VibratorManagerHooks {
+
+ /** Tells the manager to end the vibration session. */
+ void endSession(long sessionId, boolean shouldAbort);
+
+ /**
+ * Tells the manager that the vibration session is finished and the vibrators can now be
+ * used for another vibration.
+ */
+ void onSessionReleased(long sessionId);
+ }
+
+ private final Object mLock = new Object();
+ private final long mSessionId = VibrationSession.nextSessionId();
+ private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport();
+ private final int[] mVibratorIds;
+ private final long mCreateUptime;
+ private final long mCreateTime; // for debugging
+ private final IVibrationSessionCallback mCallback;
+ private final CallerInfo mCallerInfo;
+ private final VibratorManagerHooks mManagerHooks;
+ private final Handler mHandler;
+
+ @GuardedBy("mLock")
+ private Status mStatus = Status.RUNNING;
+ @GuardedBy("mLock")
+ private Status mEndStatusRequest;
+ @GuardedBy("mLock")
+ private long mStartTime; // for debugging
+ @GuardedBy("mLock")
+ private long mEndUptime;
+ @GuardedBy("mLock")
+ private long mEndTime; // for debugging
+
+ VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler,
+ @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds,
+ @NonNull IVibrationSessionCallback callback) {
+ mCreateUptime = SystemClock.uptimeMillis();
+ mCreateTime = System.currentTimeMillis();
+ mVibratorIds = vibratorIds;
+ mHandler = handler;
+ mCallback = callback;
+ mCallerInfo = callerInfo;
+ mManagerHooks = managerHooks;
+ CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this);
+ }
+
+ @Override
+ public void vibrate(CombinedVibration vibration, String reason) {
+ // TODO(b/345414356): implement vibration support
+ throw new UnsupportedOperationException("Vendor session vibrations not yet implemented");
+ }
+
+ @Override
+ public void finishSession() {
+ // Do not abort session in HAL, wait for ongoing vibration requests to complete.
+ // This might take a while to end the session, but it can be aborted by cancelSession.
+ requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+ }
+
+ @Override
+ public void cancelSession() {
+ // Always abort session in HAL while cancelling it.
+ // This might be triggered after finishSession was already called.
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+ }
+
+ @Override
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mCreateUptime;
+ }
+
+ @Override
+ public boolean isRepeating() {
+ return false;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mCallerInfo;
+ }
+
+ @Override
+ public IBinder getCallerToken() {
+ return mCallback.asBinder();
+ }
+
+ @Override
+ public DebugInfo getDebugInfo() {
+ synchronized (mLock) {
+ return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
+ mEndUptime, mEndTime);
+ }
+ }
+
+ @Override
+ public boolean wasEndRequested() {
+ synchronized (mLock) {
+ return mEndStatusRequest != null;
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
+ requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+ }
+
+ @Override
+ public void binderDied() {
+ Slog.d(TAG, "Binder died, cancelling vibration session...");
+ requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ }
+
+ @Override
+ public boolean linkToDeath() {
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error linking session to token death", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void unlinkToDeath() {
+ try {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink session to token death", e);
+ }
+ }
+
+ @Override
+ public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+ boolean immediate) {
+ // All requests to end a session should abort it to stop ongoing vibrations, even if
+ // immediate flag is false. Only the #finishSession API will not abort and wait for
+ // session vibrations to complete, which might take a long time.
+ requestEndSession(status, /* shouldAbort= */ true);
+ }
+
+ @Override
+ public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+ // TODO(b/345414356): implement vibration support
+ }
+
+ @Override
+ public void notifySyncedVibratorsCallback(long vibrationId) {
+ // TODO(b/345414356): implement vibration support
+ }
+
+ @Override
+ public void notifySessionCallback() {
+ synchronized (mLock) {
+ // If end was not requested then the HAL has cancelled the session.
+ maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+ maybeSetStatusToRequestedLocked();
+ }
+ mManagerHooks.onSessionReleased(mSessionId);
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+ + ", startTime: " + (mStartTime == 0 ? null : formatTime(mStartTime,
+ /* includeDate= */ true))
+ + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+ /* includeDate= */ true))
+ + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ + ", callerInfo: " + mCallerInfo
+ + ", vibratorIds: " + Arrays.toString(mVibratorIds);
+ }
+ }
+
+ public Status getStatus() {
+ synchronized (mLock) {
+ return mStatus;
+ }
+ }
+
+ public boolean isStarted() {
+ synchronized (mLock) {
+ return mStartTime > 0;
+ }
+ }
+
+ public boolean isEnded() {
+ synchronized (mLock) {
+ return mStatus != Status.RUNNING;
+ }
+ }
+
+ public int[] getVibratorIds() {
+ return mVibratorIds;
+ }
+
+ public ICancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ public void notifyStart() {
+ boolean isAlreadyEnded = false;
+ synchronized (mLock) {
+ if (isEnded()) {
+ // Session already ended, skip start callbacks.
+ isAlreadyEnded = true;
+ } else {
+ mStartTime = System.currentTimeMillis();
+ // Run client callback in separate thread.
+ mHandler.post(() -> {
+ try {
+ mCallback.onStarted(this);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session started", e);
+ }
+ });
+ }
+ }
+ if (isAlreadyEnded) {
+ // Session already ended, make sure we end it in the HAL.
+ mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true);
+ }
+ }
+
+ private void requestEndSession(Status status, boolean shouldAbort) {
+ boolean shouldTriggerSessionHook = false;
+ synchronized (mLock) {
+ maybeSetEndRequestLocked(status);
+ if (isStarted()) {
+ // Always trigger session hook after it has started, in case new request aborts an
+ // already finishing session. Wait for HAL callback before actually ending here.
+ shouldTriggerSessionHook = true;
+ } else {
+ // Session did not start in the HAL, end it right away.
+ maybeSetStatusToRequestedLocked();
+ }
+ }
+ if (shouldTriggerSessionHook) {
+ mManagerHooks.endSession(mSessionId, shouldAbort);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeSetEndRequestLocked(Status status) {
+ if (mEndStatusRequest != null) {
+ // End already requested, keep first requested status and time.
+ return;
+ }
+ mEndStatusRequest = status;
+ mEndTime = System.currentTimeMillis();
+ mEndUptime = SystemClock.uptimeMillis();
+ if (isStarted()) {
+ // Only trigger "finishing" callback if session started.
+ // Run client callback in separate thread.
+ mHandler.post(() -> {
+ try {
+ mCallback.onFinishing();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session is finishing", e);
+ }
+ });
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void maybeSetStatusToRequestedLocked() {
+ if (isEnded()) {
+ // End already set, keep first requested status and time.
+ return;
+ }
+ if (mEndStatusRequest == null) {
+ // No end status was requested, nothing to set.
+ return;
+ }
+ mStatus = mEndStatusRequest;
+ // Run client callback in separate thread.
+ final Status endStatus = mStatus;
+ mHandler.post(() -> {
+ try {
+ mCallback.onFinished(toSessionStatus(endStatus));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying vendor session is finishing", e);
+ }
+ });
+ }
+
+ @android.os.vibrator.VendorVibrationSession.Status
+ private static int toSessionStatus(Status status) {
+ // Exhaustive switch to cover all possible internal status.
+ return switch (status) {
+ case FINISHED
+ -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS;
+ case IGNORED_UNSUPPORTED
+ -> STATUS_UNSUPPORTED;
+ case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER,
+ CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF,
+ CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON
+ -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED;
+ case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING,
+ IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE,
+ IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
+ IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
+ -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
+ case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING,
+ IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING
+ -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
+ };
+ }
+
+ /**
+ * Holds lightweight debug information about the session that could potentially be kept in
+ * memory for a long time for bugreport dumpsys operations.
+ *
+ * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
+ * potentially expensive or resource-linked objects, such as {@link IBinder}.
+ */
+ static final class DebugInfoImpl implements VibrationSession.DebugInfo {
+ private final Status mStatus;
+ private final CallerInfo mCallerInfo;
+
+ private final long mCreateUptime;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
+ private final long mDurationMs;
+
+ DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
+ long startTime, long endUptime, long endTime) {
+ mStatus = status;
+ mCallerInfo = callerInfo;
+ mCreateUptime = createUptime;
+ mCreateTime = createTime;
+ mStartTime = startTime;
+ mEndTime = endTime;
+ mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
+ }
+
+ @Override
+ public Status getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public long getCreateUptimeMillis() {
+ return mCreateUptime;
+ }
+
+ @Override
+ public CallerInfo getCallerInfo() {
+ return mCallerInfo;
+ }
+
+ @Nullable
+ @Override
+ public Object getDumpAggregationKey() {
+ return null; // No aggregation.
+ }
+
+ @Override
+ public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ }
+
+ @Override
+ public void dump(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(VibrationProto.END_TIME, mEndTime);
+ proto.write(VibrationProto.DURATION_MS, mDurationMs);
+ proto.write(VibrationProto.STATUS, mStatus.ordinal());
+
+ final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
+ final VibrationAttributes attrs = mCallerInfo.attrs;
+ proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
+ proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
+ proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
+ proto.end(attrsToken);
+
+ proto.end(token);
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VibrationSession:");
+ pw.increaseIndent();
+ pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+ pw.println("durationMs = " + mDurationMs);
+ pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true));
+ pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true));
+ pw.println("endTime = " + (mEndTime == 0 ? null
+ : formatTime(mEndTime, /*includeDate=*/ true)));
+ pw.println("callerInfo = " + mCallerInfo);
+ pw.decreaseIndent();
+ }
+
+ @Override
+ public void dumpCompact(IndentingPrintWriter pw) {
+ // Follow pattern from Vibration.DebugInfoImpl for better debugging from dumpsys.
+ String timingsStr = String.format(Locale.ROOT,
+ "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
+ formatTime(mCreateTime, /*includeDate=*/ true),
+ "session",
+ mStatus.name().toLowerCase(Locale.ROOT),
+ mDurationMs,
+ mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false),
+ mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false));
+ String paramStr = String.format(Locale.ROOT,
+ " | flags: %4s | usage: %s",
+ Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+ mCallerInfo.attrs.usageToString());
+ // Optional, most vibrations should not be defined via AudioAttributes
+ // so skip them to simplify the logs
+ String audioUsageStr =
+ mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN
+ ? " | audioUsage=" + AudioAttributes.usageToString(
+ mCallerInfo.attrs.getOriginalAudioUsage())
+ : "";
+ String callerStr = String.format(Locale.ROOT,
+ " | %s (uid=%d, deviceId=%d) | reason: %s",
+ mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
+ pw.println(timingsStr + paramStr + audioUsageStr + callerStr);
+ }
+
+ @Override
+ public String toString() {
+ return "createTime: " + formatTime(mCreateTime, /* includeDate= */ true)
+ + ", startTime: " + formatTime(mStartTime, /* includeDate= */ true)
+ + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+ /* includeDate= */ true))
+ + ", durationMs: " + mDurationMs
+ + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ + ", callerInfo: " + mCallerInfo;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index bb2a17c698ee..27f92b2080e6 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,6 +16,8 @@
package com.android.server.vibrator;
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioAttributes;
@@ -31,9 +33,6 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -42,11 +41,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "HH:mm:ss.SSS");
- private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
- "MM-dd HH:mm:ss.SSS");
-
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -399,12 +393,5 @@ abstract class Vibration {
proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
proto.end(token);
}
-
- private String formatTime(long timeInMillis, boolean includeDate) {
- return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
- // Ensure timezone is retrieved at formatting time
- .withZone(ZoneId.systemDefault())
- .format(Instant.ofEpochMilli(timeInMillis));
- }
}
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index b511ba8be405..ae95a70e2a4f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -25,7 +25,11 @@ import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a generic vibration session that plays one or more vibration requests.
@@ -39,6 +43,16 @@ import java.util.Objects;
*/
interface VibrationSession {
+ // Used to generate globally unique session ids.
+ AtomicInteger sNextSessionId = new AtomicInteger(1); // 0 = no callback
+
+ static long nextSessionId() {
+ return sNextSessionId.getAndIncrement();
+ }
+
+ /** Returns the session id. */
+ long getSessionId();
+
/** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
long getCreateUptimeMillis();
@@ -105,6 +119,14 @@ interface VibrationSession {
void notifySyncedVibratorsCallback(long vibrationId);
/**
+ * Notify vibrator manager have completed the vibration session.
+ *
+ * <p>This will be called by the vibrator manager hardware callback indicating the session
+ * is complete, either because it was ended or cancelled by the service or the vendor.
+ */
+ void notifySessionCallback();
+
+ /**
* Session status with reference to values from vibratormanagerservice.proto for logging.
*/
enum Status {
@@ -212,6 +234,17 @@ interface VibrationSession {
*/
interface DebugInfo {
+ DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+ DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS");
+
+ static String formatTime(long timeInMillis, boolean includeDate) {
+ return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
+ // Ensure timezone is retrieved at formatting time
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(timeInMillis));
+ }
+
/** Return the vibration session status. */
Status getStatus();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ff3491182a5f..476448148e28 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -32,6 +32,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
@@ -40,6 +41,7 @@ import android.os.ExternalVibration;
import android.os.ExternalVibrationScale;
import android.os.Handler;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.IExternalVibratorService;
import android.os.IVibratorManagerService;
import android.os.IVibratorStateListener;
@@ -57,6 +59,7 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.Flags;
+import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
@@ -103,7 +106,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String VIBRATOR_CONTROL_SERVICE =
"android.frameworks.vibrator.IVibratorControlService/default";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
@@ -159,12 +162,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
new VibrationThreadCallbacks();
private final ExternalVibrationCallbacks mExternalVibrationCallbacks =
new ExternalVibrationCallbacks();
+ private final VendorVibrationSessionCallbacks mVendorVibrationSessionCallbacks =
+ new VendorVibrationSessionCallbacks();
@GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock")
- private VibrationSession mCurrentVibration;
+ private VibrationSession mCurrentSession;
@GuardedBy("mLock")
- private VibrationSession mNextVibration;
+ private VibrationSession mNextSession;
@GuardedBy("mLock")
private boolean mServiceReady;
@@ -191,14 +196,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
synchronized (mLock) {
- maybeClearCurrentAndNextVibrationsLocked(
+ maybeClearCurrentAndNextSessionsLocked(
VibratorManagerService.this::shouldCancelOnScreenOffLocked,
Status.CANCELLED_BY_SCREEN_OFF);
}
} else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
- maybeClearCurrentAndNextVibrationsLocked(
+ maybeClearCurrentAndNextSessionsLocked(
VibratorManagerService.this::shouldCancelOnFgUserRequest,
Status.CANCELLED_BY_FOREGROUND_USER);
}
@@ -215,14 +220,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return;
}
synchronized (mLock) {
- maybeClearCurrentAndNextVibrationsLocked(
+ maybeClearCurrentAndNextSessionsLocked(
VibratorManagerService.this::shouldCancelAppOpModeChangedLocked,
Status.CANCELLED_BY_APP_OPS);
}
}
};
- static native long nativeInit(OnSyncedVibrationCompleteListener listener);
+ static native long nativeInit(VibratorManagerNativeCallbacks listener);
static native long nativeGetFinalizer();
@@ -236,6 +241,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
static native void nativeCancelSynced(long nativeServicePtr);
+ static native boolean nativeStartSession(long nativeServicePtr, long sessionId,
+ int[] vibratorIds);
+
+ static native void nativeEndSession(long nativeServicePtr, long sessionId, boolean shouldAbort);
+
+ static native void nativeClearSessions(long nativeServicePtr);
+
@VisibleForTesting
VibratorManagerService(Context context, Injector injector) {
mContext = context;
@@ -303,6 +315,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Reset the hardware to a default state, in case this is a runtime restart instead of a
// fresh boot.
mNativeWrapper.cancelSynced();
+ if (Flags.vendorVibrationEffects()) {
+ mNativeWrapper.clearSessions();
+ }
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).reset();
}
@@ -363,6 +378,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override // Binder call
+ public int getCapabilities() {
+ return (int) mCapabilities;
+ }
+
+ @Override // Binder call
@Nullable
public VibratorInfo getVibratorInfo(int vibratorId) {
final VibratorController controller = mVibrators.get(vibratorId);
@@ -590,11 +610,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
return null;
}
- if (effect.hasVendorEffects()
- && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
- Slog.e(TAG, "vibrate; no permission for vendor effects");
- logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
- return null;
+ if (effect.hasVendorEffects()) {
+ if (!Flags.vendorVibrationEffects()) {
+ Slog.e(TAG, "vibrate; vendor effects feature disabled");
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
+ return null;
+ }
+ if (!hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
+ Slog.e(TAG, "vibrate; no permission for vendor effects");
+ logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
+ return null;
+ }
}
enforceUpdateAppOpsStatsPermission(uid);
if (!isEffectValid(effect)) {
@@ -623,7 +649,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Check if ongoing vibration is more important than this vibration.
if (ignoreStatus == null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
if (vibrationEndInfo != null) {
ignoreStatus = vibrationEndInfo.status;
ignoredBy = vibrationEndInfo.endedBy;
@@ -634,8 +660,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (ignoreStatus == null) {
final long ident = Binder.clearCallingIdentity();
try {
- if (mCurrentVibration != null) {
- if (shouldPipelineVibrationLocked(mCurrentVibration, vib)) {
+ if (mCurrentSession != null) {
+ if (shouldPipelineVibrationLocked(mCurrentSession, vib)) {
// Don't cancel the current vibration if it's pipeline-able.
// Note that if there is a pending next vibration that can't be
// pipelined, it will have already cancelled the current one, so we
@@ -645,12 +671,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
} else {
vib.stats.reportInterruptedAnotherVibration(
- mCurrentVibration.getCallerInfo());
- mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
+ mCurrentSession.getCallerInfo());
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
/* immediate= */ false);
}
}
- clearNextVibrationLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+ clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
ignoreStatus = startVibrationLocked(session);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -659,7 +685,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Ignored or failed to start the vibration, end it and report metrics right away.
if (ignoreStatus != null) {
- endVibrationLocked(session, ignoreStatus, ignoredBy);
+ endSessionLocked(session, ignoreStatus, ignoredBy);
}
return vib;
}
@@ -681,19 +707,154 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
try {
// TODO(b/370948466): investigate why token not checked on external vibrations.
IBinder cancelToken =
- (mNextVibration instanceof ExternalVibrationSession) ? null : token;
- if (shouldCancelVibration(mNextVibration, usageFilter, cancelToken)) {
- clearNextVibrationLocked(Status.CANCELLED_BY_USER);
+ (mNextSession instanceof ExternalVibrationSession) ? null : token;
+ if (shouldCancelSession(mNextSession, usageFilter, cancelToken)) {
+ clearNextSessionLocked(Status.CANCELLED_BY_USER);
}
cancelToken =
- (mCurrentVibration instanceof ExternalVibrationSession) ? null : token;
- if (shouldCancelVibration(mCurrentVibration, usageFilter, cancelToken)) {
- mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
+ (mCurrentSession instanceof ExternalVibrationSession) ? null : token;
+ if (shouldCancelSession(mCurrentSession, usageFilter, cancelToken)) {
+ mCurrentSession.requestEnd(Status.CANCELLED_BY_USER);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @android.annotation.EnforcePermission(allOf = {
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ android.Manifest.permission.START_VIBRATION_SESSIONS,
+ })
+ @Override // Binder call
+ public ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
+ int[] vibratorIds, VibrationAttributes attrs, String reason,
+ IVibrationSessionCallback callback) {
+ startVendorVibrationSession_enforcePermission();
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationSession");
+ try {
+ VendorVibrationSession session = startVendorVibrationSessionInternal(
+ uid, deviceId, opPkg, vibratorIds, attrs, reason, callback);
+ return session == null ? null : session.getCancellationSignal();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ VendorVibrationSession startVendorVibrationSessionInternal(int uid, int deviceId, String opPkg,
+ int[] vibratorIds, VibrationAttributes attrs, String reason,
+ IVibrationSessionCallback callback) {
+ if (!Flags.vendorVibrationEffects()) {
+ throw new UnsupportedOperationException("Vibration sessions not supported");
+ }
+ attrs = fixupVibrationAttributes(attrs, /* effect= */ null);
+ CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason);
+ if (callback == null) {
+ Slog.e(TAG, "session callback must not be null");
+ logAndRecordSessionAttempt(callerInfo, Status.IGNORED_ERROR_TOKEN);
+ return null;
+ }
+ if (vibratorIds == null) {
+ vibratorIds = new int[0];
+ }
+ enforceUpdateAppOpsStatsPermission(uid);
+ VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler,
+ mVendorVibrationSessionCallbacks, vibratorIds, callback);
+
+ if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+ // Force update of user settings before checking if this vibration effect should
+ // be ignored or scaled.
+ mVibrationSettings.update();
+ }
+
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Starting session " + session.getSessionId());
+ }
+
+ Status ignoreStatus = null;
+ CallerInfo ignoredBy = null;
+
+ // Check if HAL has capability to start sessions.
+ if ((mCapabilities & IVibratorManager.CAP_START_SESSIONS) == 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Missing capability to start sessions, ignoring request");
+ }
+ ignoreStatus = Status.IGNORED_UNSUPPORTED;
+ }
+
+ // Check if any vibrator ID was requested.
+ if (ignoreStatus == null && vibratorIds.length == 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Empty vibrator ids to start session, ignoring request");
+ }
+ ignoreStatus = Status.IGNORED_UNSUPPORTED;
+ }
+
+ // Check if user settings or DnD is set to ignore this session.
+ if (ignoreStatus == null) {
+ ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+ }
+
+ // Check if ongoing vibration is more important than this session.
+ if (ignoreStatus == null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
+ if (vibrationEndInfo != null) {
+ ignoreStatus = vibrationEndInfo.status;
+ ignoredBy = vibrationEndInfo.endedBy;
+ }
+ }
+
+ if (ignoreStatus == null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ // If not ignored so far then stop ongoing sessions before starting this one.
+ clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+ if (mCurrentSession != null) {
+ mNextSession = session;
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
+ /* immediate= */ false);
+ } else {
+ ignoreStatus = startVendorSessionLocked(session);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+
+ // Ignored or failed to start the session, end it and report metrics right away.
+ if (ignoreStatus != null) {
+ endSessionLocked(session, ignoreStatus, ignoredBy);
+ }
+ return session;
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private Status startVendorSessionLocked(VendorVibrationSession session) {
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked");
+ try {
+ if (session.isEnded()) {
+ // Session already ended, possibly cancelled by app cancellation signal.
+ return session.getStatus();
+ }
+ if (!session.linkToDeath()) {
+ return Status.IGNORED_ERROR_TOKEN;
+ }
+ if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) {
+ Slog.e(TAG, "Error starting session " + session.getSessionId()
+ + " on vibrators " + Arrays.toString(session.getVibratorIds()));
+ session.unlinkToDeath();
+ return Status.IGNORED_UNSUPPORTED;
+ }
+ session.notifyStart();
+ mCurrentSession = session;
+ return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
}
@@ -747,8 +908,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println("CurrentVibration:");
pw.increaseIndent();
- if (mCurrentVibration != null) {
- mCurrentVibration.getDebugInfo().dump(pw);
+ if (mCurrentSession != null) {
+ mCurrentSession.getDebugInfo().dump(pw);
} else {
pw.println("null");
}
@@ -757,8 +918,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
pw.println("NextVibration:");
pw.increaseIndent();
- if (mNextVibration != null) {
- mNextVibration.getDebugInfo().dump(pw);
+ if (mNextSession != null) {
+ mNextSession.getDebugInfo().dump(pw);
} else {
pw.println("null");
}
@@ -782,8 +943,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
synchronized (mLock) {
mVibrationSettings.dump(proto);
mVibrationScaler.dump(proto);
- if (mCurrentVibration != null) {
- mCurrentVibration.getDebugInfo().dump(proto,
+ if (mCurrentSession != null) {
+ mCurrentSession.getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
for (int i = 0; i < mVibrators.size(); i++) {
@@ -816,18 +977,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// TODO(b/372241975): investigate why external vibrations were not handled here before
- if (mCurrentVibration == null
- || (mCurrentVibration instanceof ExternalVibrationSession)) {
+ if (mCurrentSession == null
+ || (mCurrentSession instanceof ExternalVibrationSession)) {
return;
}
- Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+ Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentSession.getCallerInfo());
if (inputDevicesChanged || (ignoreStatus != null)) {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration because settings changed: "
+ (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
+ mCurrentSession.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
}
}
}
@@ -866,15 +1027,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mInputDeviceDelegate.isAvailable()) {
return startVibrationOnInputDevicesLocked(session.getVibration());
}
- if (mCurrentVibration == null) {
+ if (mCurrentSession == null) {
return startVibrationOnThreadLocked(session);
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
// Note that we don't consider pipelining here, because new pipelined ones should
// replace pending non-executing pipelined ones anyway.
- clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
- mNextVibration = session;
+ clearNextSessionLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+ mNextSession = session;
return null;
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -891,16 +1052,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
// Make sure mCurrentVibration is set while triggering the VibrationThread.
- mCurrentVibration = session;
- if (!mCurrentVibration.linkToDeath()) {
+ mCurrentSession = session;
+ if (!mCurrentSession.linkToDeath()) {
// Shouldn't happen. The method call already logs.
- mCurrentVibration = null; // Aborted.
+ mCurrentSession = null; // Aborted.
return Status.IGNORED_ERROR_TOKEN;
}
if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
// Shouldn't happen. The method call already logs.
session.setVibrationConductor(null); // Rejected by thread, clear it in session.
- mCurrentVibration = null; // Aborted.
+ mCurrentSession = null; // Aborted.
return Status.IGNORED_ERROR_SCHEDULING;
}
return null;
@@ -914,23 +1075,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private void maybeStartNextSingleVibrationLocked() {
- if (mNextVibration instanceof SingleVibrationSession session) {
- mNextVibration = null;
+ private void maybeStartNextSessionLocked() {
+ if (mNextSession instanceof SingleVibrationSession session) {
+ mNextSession = null;
Status errorStatus = startVibrationOnThreadLocked(session);
if (errorStatus != null) {
- endVibrationLocked(session, errorStatus);
+ endSessionLocked(session, errorStatus);
}
- }
+ } else if (mNextSession instanceof VendorVibrationSession session) {
+ mNextSession = null;
+ Status errorStatus = startVendorSessionLocked(session);
+ if (errorStatus != null) {
+ endSessionLocked(session, errorStatus);
+ }
+ } // External vibrations cannot be started asynchronously.
}
@GuardedBy("mLock")
- private void endVibrationLocked(VibrationSession session, Status status) {
- endVibrationLocked(session, status, /* endedBy= */ null);
+ private void endSessionLocked(VibrationSession session, Status status) {
+ endSessionLocked(session, status, /* endedBy= */ null);
}
@GuardedBy("mLock")
- private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+ private void endSessionLocked(VibrationSession session, Status status, CallerInfo endedBy) {
session.requestEnd(status, endedBy, /* immediate= */ false);
logAndRecordVibration(session.getDebugInfo());
}
@@ -975,6 +1142,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibrationScaler.ADAPTIVE_SCALE_NONE));
}
+ private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) {
+ logAndRecordVibration(
+ new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
+ SystemClock.uptimeMillis(), System.currentTimeMillis(),
+ /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0));
+ }
+
private void logAndRecordVibration(DebugInfo info) {
info.logMetrics(mFrameworkStatsLogger);
logVibrationStatus(info.getCallerInfo().uid, info.getCallerInfo().attrs, info.getStatus());
@@ -1026,25 +1200,40 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
+ private void onVibrationSessionComplete(long sessionId) {
+ synchronized (mLock) {
+ if (mCurrentSession == null || mCurrentSession.getSessionId() != sessionId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " callback ignored");
+ }
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " complete, notifying session");
+ }
+ mCurrentSession.notifySessionCallback();
+ }
+ }
+
private void onSyncedVibrationComplete(long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null) {
+ if (mCurrentSession != null) {
if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
}
- mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
+ mCurrentSession.notifySyncedVibratorsCallback(vibrationId);
}
}
}
private void onVibrationComplete(int vibratorId, long vibrationId) {
synchronized (mLock) {
- if (mCurrentVibration != null) {
+ if (mCurrentSession != null) {
if (DEBUG) {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread");
}
- mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
+ mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
}
}
}
@@ -1056,10 +1245,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*/
@GuardedBy("mLock")
@Nullable
- private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
- if (mNextVibration != null) {
- Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
- mNextVibration);
+ private Vibration.EndInfo shouldIgnoreForOngoingLocked(VibrationSession session) {
+ if (mNextSession != null) {
+ Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoing(session,
+ mNextSession);
if (vibrationEndInfo != null) {
// Next vibration has higher importance than the new one, so the new vibration
// should be ignored.
@@ -1067,13 +1256,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- if (mCurrentVibration != null) {
- if (mCurrentVibration.wasEndRequested()) {
+ if (mCurrentSession != null) {
+ if (mCurrentSession.wasEndRequested()) {
// Current session has ended or is cancelling, should not block incoming vibrations.
return null;
}
- return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
+ return shouldIgnoreForOngoing(session, mCurrentSession);
}
return null;
@@ -1086,7 +1275,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
*/
@Nullable
- private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
+ private static Vibration.EndInfo shouldIgnoreForOngoing(
@NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
int newSessionImportance = getVibrationImportance(newSession);
@@ -1214,11 +1403,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* @param tokenFilter The binder token to identify the vibration origin. Only vibrations
* started with the same token can be cancelled with it.
*/
- private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+ private boolean shouldCancelSession(@Nullable VibrationSession session, int usageFilter,
@Nullable IBinder tokenFilter) {
if (session == null) {
return false;
}
+ if (session instanceof VendorVibrationSession) {
+ // Vendor sessions should not be cancelled by Vibrator.cancel API.
+ return false;
+ }
if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
// Vibration from a different app, this should not cancel it.
return false;
@@ -1572,10 +1765,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
try {
synchronized (mLock) {
- if (!(mCurrentVibration instanceof SingleVibrationSession session)) {
+ if (!(mCurrentSession instanceof SingleVibrationSession session)) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
- + " currentSession=" + mCurrentVibration);
+ + " currentSession=" + mCurrentSession);
}
// Only single vibration sessions are ended by thread being released. Abort.
return;
@@ -1586,11 +1779,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ " expected=%d, released=%d",
session.getVibration().id, vibrationId));
}
- finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
- clearCurrentVibrationLocked();
+ finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+ clearCurrentSessionLocked();
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- // Start next vibration if it's a single vibration waiting for the thread.
- maybeStartNextSingleVibrationLocked();
+ // Start next vibration if it's waiting for the thread.
+ maybeStartNextSessionLocked();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1613,10 +1806,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationReleased");
try {
synchronized (mLock) {
- if (!(mCurrentVibration instanceof ExternalVibrationSession session)) {
+ if (!(mCurrentSession instanceof ExternalVibrationSession session)) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, "VibrationSession invalid on external vibration release."
- + " currentSession=" + mCurrentVibration);
+ + " currentSession=" + mCurrentSession);
}
// Only external vibration sessions are ended by this callback. Abort.
return;
@@ -1627,10 +1820,62 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ " expected=%d, released=%d", session.id, vibrationId));
}
setExternalControl(false, session.stats);
- clearCurrentVibrationLocked();
- // Start next vibration if it's a single vibration waiting for the external
- // control to be over.
- maybeStartNextSingleVibrationLocked();
+ clearCurrentSessionLocked();
+ // Start next vibration if it's waiting for the external control to be over.
+ maybeStartNextSessionLocked();
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+ }
+
+ /**
+ * Implementation of {@link ExternalVibrationSession.VibratorManagerHooks} that controls
+ * external vibrations and reports them when finished.
+ */
+ private final class VendorVibrationSessionCallbacks
+ implements VendorVibrationSession.VibratorManagerHooks {
+
+ @Override
+ public void endSession(long sessionId, boolean shouldAbort) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId
+ + (shouldAbort ? " aborting" : " ending"));
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "endSession");
+ try {
+ mNativeWrapper.endSession(sessionId, shouldAbort);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIBRATOR);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(long sessionId) {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibration session " + sessionId + " released");
+ }
+ Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVendorSessionReleased");
+ try {
+ synchronized (mLock) {
+ if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+ if (Build.IS_DEBUGGABLE) {
+ Slog.wtf(TAG, "VibrationSession invalid on vibration session release."
+ + " currentSession=" + mCurrentSession);
+ }
+ // Only vendor vibration sessions are ended by this callback. Abort.
+ return;
+ }
+ if (Build.IS_DEBUGGABLE && (session.getSessionId() != sessionId)) {
+ Slog.wtf(TAG, TextUtils.formatSimple(
+ "SessionId mismatch on vendor vibration session release."
+ + " expected=%d, released=%d",
+ session.getSessionId(), sessionId));
+ }
+ clearCurrentSessionLocked();
+ // Start next vibration if it's waiting for the HAL session to be over.
+ maybeStartNextSessionLocked();
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1638,19 +1883,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- /** Listener for synced vibration completion callbacks from native. */
+ /** Listener for vibrator manager completion callbacks from native. */
@VisibleForTesting
- interface OnSyncedVibrationCompleteListener {
+ interface VibratorManagerNativeCallbacks {
/** Callback triggered when synced vibration is complete. */
- void onComplete(long vibrationId);
+ void onSyncedVibrationComplete(long vibrationId);
+
+ /** Callback triggered when vibration session is complete. */
+ void onVibrationSessionComplete(long sessionId);
}
/**
* Implementation of listeners to native vibrators with a weak reference to this service.
*/
private static final class VibrationCompleteListener implements
- VibratorController.OnVibrationCompleteListener, OnSyncedVibrationCompleteListener {
+ VibratorController.OnVibrationCompleteListener, VibratorManagerNativeCallbacks {
private WeakReference<VibratorManagerService> mServiceRef;
VibrationCompleteListener(VibratorManagerService service) {
@@ -1658,7 +1906,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
- public void onComplete(long vibrationId) {
+ public void onSyncedVibrationComplete(long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
service.onSyncedVibrationComplete(vibrationId);
@@ -1666,6 +1914,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@Override
+ public void onVibrationSessionComplete(long sessionId) {
+ VibratorManagerService service = mServiceRef.get();
+ if (service != null) {
+ service.onVibrationSessionComplete(sessionId);
+ }
+ }
+
+ @Override
public void onComplete(int vibratorId, long vibrationId) {
VibratorManagerService service = mServiceRef.get();
if (service != null) {
@@ -1698,7 +1954,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private long mNativeServicePtr = 0;
/** Returns native pointer to newly created controller and connects with HAL service. */
- public void init(OnSyncedVibrationCompleteListener listener) {
+ public void init(VibratorManagerNativeCallbacks listener) {
mNativeServicePtr = nativeInit(listener);
long finalizerPtr = nativeGetFinalizer();
@@ -1734,6 +1990,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void cancelSynced() {
nativeCancelSynced(mNativeServicePtr);
}
+
+ /** Start vibration session. */
+ public boolean startSession(long sessionId, @NonNull int[] vibratorIds) {
+ return nativeStartSession(mNativeServicePtr, sessionId, vibratorIds);
+ }
+
+ /** End vibration session. */
+ public void endSession(long sessionId, boolean shouldAbort) {
+ nativeEndSession(mNativeServicePtr, sessionId, shouldAbort);
+ }
+
+ /** Clear vibration sessions. */
+ public void clearSessions() {
+ nativeClearSessions(mNativeServicePtr);
+ }
}
/** Keep records of vibrations played and provide debug information for this service. */
@@ -1853,46 +2124,46 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Status status) {
- clearNextVibrationLocked(status, /* endedBy= */ null);
+ private void clearNextSessionLocked(Status status) {
+ clearNextSessionLocked(status, /* endedBy= */ null);
}
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
- if (mNextVibration != null) {
+ private void clearNextSessionLocked(Status status, CallerInfo endedBy) {
+ if (mNextSession != null) {
if (DEBUG) {
- Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+ Slog.d(TAG, "Dropping pending vibration from " + mNextSession.getCallerInfo()
+ " with status: " + status);
}
// Clearing next vibration before playing it, end it and report metrics right away.
- endVibrationLocked(mNextVibration, status, endedBy);
- mNextVibration = null;
+ endSessionLocked(mNextSession, status, endedBy);
+ mNextSession = null;
}
}
/** Clears mCurrentVibration if set, reporting metrics */
@GuardedBy("mLock")
- private void clearCurrentVibrationLocked() {
- if (mCurrentVibration != null) {
- mCurrentVibration.unlinkToDeath();
- logAndRecordVibration(mCurrentVibration.getDebugInfo());
- mCurrentVibration = null;
+ private void clearCurrentSessionLocked() {
+ if (mCurrentSession != null) {
+ mCurrentSession.unlinkToDeath();
+ logAndRecordVibration(mCurrentSession.getDebugInfo());
+ mCurrentSession = null;
mLock.notify(); // Notify if waiting for current vibration to end.
}
}
@GuardedBy("mLock")
- private void maybeClearCurrentAndNextVibrationsLocked(
+ private void maybeClearCurrentAndNextSessionsLocked(
Predicate<VibrationSession> shouldEndSessionPredicate, Status endStatus) {
// TODO(b/372241975): investigate why external vibrations were not handled here before
- if (!(mNextVibration instanceof ExternalVibrationSession)
- && shouldEndSessionPredicate.test(mNextVibration)) {
- clearNextVibrationLocked(endStatus);
+ if (!(mNextSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mNextSession)) {
+ clearNextSessionLocked(endStatus);
}
- if (!(mCurrentVibration instanceof ExternalVibrationSession)
- && shouldEndSessionPredicate.test(mCurrentVibration)) {
- mCurrentVibration.requestEnd(endStatus);
+ if (!(mCurrentSession instanceof ExternalVibrationSession)
+ && shouldEndSessionPredicate.test(mCurrentSession)) {
+ mCurrentSession.requestEnd(endStatus);
}
}
@@ -1902,12 +2173,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
*
* @return true if the vibration completed, or false if waiting timed out.
*/
- public boolean waitForCurrentVibrationEnd(long maxWaitMillis) {
+ public boolean waitForCurrentSessionEnd(long maxWaitMillis) {
long now = SystemClock.elapsedRealtime();
long deadline = now + maxWaitMillis;
synchronized (mLock) {
while (true) {
- if (mCurrentVibration == null) {
+ if (mCurrentSession == null) {
return true; // Done
}
if (now >= deadline) { // Note that thread.wait(0) waits indefinitely.
@@ -1965,7 +2236,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
synchronized (mLock) {
if (!hasExternalControlCapability()) {
- endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+ endSessionLocked(session, Status.IGNORED_UNSUPPORTED);
return session.getScale();
}
@@ -1976,17 +2247,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+ " tried to play externally controlled vibration"
+ " without VIBRATE permission, ignoring.");
- endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+ endSessionLocked(session, Status.IGNORED_MISSING_PERMISSION);
return session.getScale();
}
Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
if (ignoreStatus != null) {
- endVibrationLocked(session, ignoreStatus);
+ endSessionLocked(session, ignoreStatus);
return session.getScale();
}
- if ((mCurrentVibration instanceof ExternalVibrationSession evs)
+ if ((mCurrentSession instanceof ExternalVibrationSession evs)
&& evs.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
@@ -1994,17 +2265,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// Check if ongoing vibration is more important than this vibration.
- Vibration.EndInfo ignoreInfo = shouldIgnoreVibrationForOngoingLocked(session);
+ Vibration.EndInfo ignoreInfo = shouldIgnoreForOngoingLocked(session);
if (ignoreInfo != null) {
- endVibrationLocked(session, ignoreInfo.status, ignoreInfo.endedBy);
+ endSessionLocked(session, ignoreInfo.status, ignoreInfo.endedBy);
return session.getScale();
}
// First clear next request, so it won't start when the current one ends.
- clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo);
- mNextVibration = session;
+ clearNextSessionLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo);
+ mNextSession = session;
- if (mCurrentVibration != null) {
+ if (mCurrentSession != null) {
// Cancel any vibration that may be playing and ready the vibrator, even if
// we have an externally controlled vibration playing already.
// Since the interface defines that only one externally controlled
@@ -2016,36 +2287,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// as we would need to mute the old one still if it came from a different
// controller.
session.stats.reportInterruptedAnotherVibration(
- mCurrentVibration.getCallerInfo());
- mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+ mCurrentSession.getCallerInfo());
+ mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED,
session.callerInfo, /* immediate= */ true);
waitForCompletion = true;
}
}
// Wait for lock and interact with HAL to set external control outside main lock.
if (waitForCompletion) {
- if (!waitForCurrentVibrationEnd(VIBRATION_CANCEL_WAIT_MILLIS)) {
+ if (!waitForCurrentSessionEnd(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- if (mNextVibration == session) {
- mNextVibration = null;
+ if (mNextSession == session) {
+ mNextSession = null;
}
- endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+ endSessionLocked(session, Status.IGNORED_ERROR_CANCELLING);
return session.getScale();
}
}
}
synchronized (mLock) {
- if (mNextVibration == session) {
+ if (mNextSession == session) {
// This is still the next vibration to be played.
- mNextVibration = null;
+ mNextSession = null;
} else {
// A new request took the place of this one, maybe with higher importance.
// Next vibration already cleared with the right status, just return here.
return session.getScale();
}
if (!session.linkToDeath()) {
- endVibrationLocked(session, Status.IGNORED_ERROR_TOKEN);
+ endSessionLocked(session, Status.IGNORED_ERROR_TOKEN);
return session.getScale();
}
if (DEBUG) {
@@ -2062,7 +2333,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// should be ignored or scaled.
mVibrationSettings.update();
}
- mCurrentVibration = session;
+ mCurrentSession = session;
session.scale(mVibrationScaler, attrs.getUsage());
// Vibrator will start receiving data from external channels after this point.
@@ -2080,12 +2351,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
try {
synchronized (mLock) {
- if ((mCurrentVibration instanceof ExternalVibrationSession evs)
+ if ((mCurrentSession instanceof ExternalVibrationSession evs)
&& evs.isHoldingSameVibration(vib)) {
if (DEBUG) {
Slog.d(TAG, "Stopping external vibration: " + vib);
}
- mCurrentVibration.requestEnd(Status.FINISHED);
+ mCurrentSession.requestEnd(Status.FINISHED);
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 73ae51c6e64a..14be59f27f84 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -254,6 +254,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -10308,6 +10309,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) {
pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top);
}
+
+ if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+ PackageManager pm = mAtmService.mContext.getPackageManager();
+ if (pictureInPictureArgs.isAutoEnterEnabled()
+ && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName)
+ == PackageManager.PERMISSION_DENIED) {
+ Log.i(TAG,
+ "Auto-enter PiP only allowed on TV if android.permission"
+ + ".TV_IMPLICIT_ENTER_PIP permission is held by the app.");
+ PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+ builder.setAutoEnterEnabled(false);
+ pictureInPictureArgs.copyOnlySet(builder.build());
+ }
+ }
}
private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index ec171c5e5766..a71620d66f80 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,10 +46,8 @@ import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balAdditionalStartModes;
import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balImprovedMetrics;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static com.android.window.flags.Flags.balStrictModeRo;
@@ -348,11 +346,7 @@ public class BackgroundActivityStartController {
@BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
checkedOptions.getPendingIntentBackgroundActivityStartMode();
- if (!balImproveRealCallerVisibilityCheck()) {
- // without this fix the auto-opt ins below would violate CTS tests
- mAutoOptInReason = null;
- mAutoOptInCaller = false;
- } else if (originatingPendingIntent == null) {
+ if (originatingPendingIntent == null) {
mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
mAutoOptInCaller = true;
} else if (mIsCallForResult) {
@@ -599,12 +593,8 @@ public class BackgroundActivityStartController {
mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
}
// features
- sb.append("; balImproveRealCallerVisibilityCheck: ")
- .append(balImproveRealCallerVisibilityCheck());
sb.append("; balRequireOptInByPendingIntentCreator: ")
.append(balRequireOptInByPendingIntentCreator());
- sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
- .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
.append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
@@ -1133,23 +1123,13 @@ public class BackgroundActivityStartController {
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
- if (balImproveRealCallerVisibilityCheck()) {
- if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has visible window");
- }
- if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has non-app visible window");
- }
- } else {
- // don't abort if the realCallingUid has a visible window
- // TODO(b/171459802): We should check appSwitchAllowed also
- if (state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false,
- "realCallingUid has visible (non-toast) window.");
- }
+ if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has visible window");
+ }
+ if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
+ return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has non-app visible window");
}
// Don't abort if the realCallerApp or other processes of that uid are considered to be in
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 264c8beb44bf..ccf1aedb3177 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -50,7 +50,6 @@ import android.util.IntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -137,10 +136,8 @@ class BackgroundLaunchProcessController {
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
- if (checkConfiguration.checkVisibility && (
- Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
- ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
- : isBoundByForegroundUid())) {
+ if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
+ && isBoundByForegroundUid()) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
: BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a6034664af5a..20481f25fa5c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -465,6 +465,31 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
+ /**
+ * This ensures that all changes for previously transient-hide containers are flagged such that
+ * they will report changes and be included in this transition.
+ */
+ void updateChangesForRestoreTransientHideTasks(Transition transientLaunchTransition) {
+ if (transientLaunchTransition.mTransientHideTasks == null) {
+ // Skip if the transient-launch transition has no transient-hide tasks
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Skipping update changes for restore transient hide tasks");
+ return;
+ }
+
+ // For each change, if it was previously transient-hidden, then we should force a flag to
+ // ensure that it is included in the next transition
+ for (int i = 0; i < mChanges.size(); i++) {
+ final WindowContainer container = mChanges.keyAt(i);
+ if (transientLaunchTransition.isInTransientHide(container)) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Force update transient hide task for restore %d: %s", mSyncId, container);
+ final ChangeInfo info = mChanges.valueAt(i);
+ info.mRestoringTransientHide = true;
+ }
+ }
+ }
+
/** Returns {@code true} if the task should keep visible if this is a transient transition. */
boolean isTransientVisible(@NonNull Task task) {
if (mTransientLaunches == null) return false;
@@ -3478,6 +3503,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// State tracking
boolean mExistenceChanged = false;
+ // This state indicates that we are restoring transient order as a part of an
+ // end-transition. Because the visibility for transient hide containers has not actually
+ // changed, we need to ensure that hasChanged() still reports the relevant changes
+ boolean mRestoringTransientHide = false;
// before change state
boolean mVisible;
int mWindowingMode;
@@ -3552,7 +3581,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|| !mContainer.getBounds().equals(mAbsoluteBounds)
|| mRotation != mContainer.getWindowConfiguration().getRotation()
|| mDisplayId != getDisplayId(mContainer)
- || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0;
+ || (mFlags & ChangeInfo.FLAG_CHANGE_MOVED_TO_TOP) != 0
+ // If we are restoring transient-hide containers, then we should consider them
+ // important for the transition as well (their requested visibilities would not
+ // have changed for the checks below to consider it).
+ || mRestoringTransientHide;
}
@TransitionInfo.TransitionMode
@@ -3565,6 +3598,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
final boolean nowVisible = wc.isVisibleRequested();
if (nowVisible == mVisible) {
+ if (mRestoringTransientHide) {
+ // The requested visibility has not changed for transient-hide containers, but
+ // we are restoring them so we should considering them moving to front again
+ return TRANSIT_TO_FRONT;
+ }
return TRANSIT_CHANGE;
}
if (mExistenceChanged) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 87bdfa4f5d75..143d1b72fff9 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -524,6 +525,23 @@ class TransitionController {
return false;
}
+ /**
+ * @return A pair of the transition and restore-behind target for the given {@param container}.
+ * @param container An ancestor of a transient-launch activity
+ */
+ @Nullable
+ Pair<Transition, Task> getTransientLaunchTransitionAndTarget(
+ @NonNull WindowContainer container) {
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ final Transition transition = mPlayingTransitions.get(i);
+ final Task restoreBehindTask = transition.getTransientLaunchRestoreTarget(container);
+ if (restoreBehindTask != null) {
+ return new Pair<>(transition, restoreBehindTask);
+ }
+ }
+ return null;
+ }
+
/** Returns {@code true} if the display contains a transient-launch transition. */
boolean hasTransientLaunch(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dac8f69a4cae..ead12826c263 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -111,6 +111,7 @@ import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
@@ -1375,16 +1376,56 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
}
case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: {
- if (!chain.isFinishing()) break;
+ if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ // Only allow restoring transient order when finishing a transition
+ if (!chain.isFinishing()) break;
+ }
+ // Validate the container
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
- if (container == null) break;
+ if (container == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: invalid container");
+ break;
+ }
final Task thisTask = container.asActivityRecord() != null
? container.asActivityRecord().getTask() : container.asTask();
- if (thisTask == null) break;
- final Task restoreAt = chain.mTransition.getTransientLaunchRestoreTarget(container);
- if (restoreAt == null) break;
+ if (thisTask == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: invalid task");
+ break;
+ }
+
+ // Find the task to restore behind
+ final Pair<Transition, Task> transientRestore =
+ mTransitionController.getTransientLaunchTransitionAndTarget(container);
+ if (transientRestore == null) {
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: no restore task");
+ break;
+ }
+ final Transition transientLaunchTransition = transientRestore.first;
+ final Task restoreAt = transientRestore.second;
+ ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Restoring transient order: restoring behind task=%d", restoreAt.mTaskId);
+
+ // Restore the position of the given container behind the target task
final TaskDisplayArea taskDisplayArea = thisTask.getTaskDisplayArea();
taskDisplayArea.moveRootTaskBehindRootTask(thisTask.getRootTask(), restoreAt);
+
+ if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ // Because we are in a transient launch transition, the requested visibility of
+ // tasks does not actually change for the transient-hide tasks, but we do want
+ // the restoration of these transient-hide tasks to top to be a part of this
+ // finish transition
+ final Transition collectingTransition =
+ mTransitionController.getCollectingTransition();
+ if (collectingTransition != null) {
+ collectingTransition.updateChangesForRestoreTransientHideTasks(
+ transientLaunchTransition);
+ }
+ }
+
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
}
case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: {
diff --git a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index a47ab9d27c17..46be79e7c097 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -16,27 +16,32 @@
#define LOG_TAG "VibratorManagerService"
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "core_jni_helpers.h"
-#include "jni.h"
+#include "com_android_server_vibrator_VibratorManagerService.h"
+#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
#include <utils/misc.h>
-
#include <vibratorservice/VibratorManagerHalController.h>
-#include "com_android_server_vibrator_VibratorManagerService.h"
+#include <unordered_map>
+
+#include "android_runtime/AndroidRuntime.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
namespace android {
static JavaVM* sJvm = nullptr;
-static jmethodID sMethodIdOnComplete;
+static jmethodID sMethodIdOnSyncedVibrationComplete;
+static jmethodID sMethodIdOnVibrationSessionComplete;
static std::mutex gManagerMutex;
static vibrator::ManagerHalController* gManager GUARDED_BY(gManagerMutex) = nullptr;
class NativeVibratorManagerService {
public:
+ using IVibrationSession = aidl::android::hardware::vibrator::IVibrationSession;
+ using VibrationSessionConfig = aidl::android::hardware::vibrator::VibrationSessionConfig;
+
NativeVibratorManagerService(JNIEnv* env, jobject callbackListener)
: mHal(std::make_unique<vibrator::ManagerHalController>()),
mCallbackListener(env->NewGlobalRef(callbackListener)) {
@@ -52,15 +57,69 @@ public:
vibrator::ManagerHalController* hal() const { return mHal.get(); }
- std::function<void()> createCallback(jlong vibrationId) {
+ std::function<void()> createSyncedVibrationCallback(jlong vibrationId) {
return [vibrationId, this]() {
auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
- jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnSyncedVibrationComplete,
+ vibrationId);
};
}
+ std::function<void()> createVibrationSessionCallback(jlong sessionId) {
+ return [sessionId, this]() {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnVibrationSessionComplete,
+ sessionId);
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ mSessions.erase(it);
+ }
+ };
+ }
+
+ bool startSession(jlong sessionId, const std::vector<int32_t>& vibratorIds) {
+ VibrationSessionConfig config;
+ auto callback = createVibrationSessionCallback(sessionId);
+ auto result = hal()->startSession(vibratorIds, config, callback);
+ if (!result.isOk()) {
+ return false;
+ }
+
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ mSessions[sessionId] = std::move(result.value());
+ return true;
+ }
+
+ void closeSession(jlong sessionId) {
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ it->second->close();
+ // Keep session, it can still be aborted.
+ }
+ }
+
+ void abortSession(jlong sessionId) {
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ auto it = mSessions.find(sessionId);
+ if (it != mSessions.end()) {
+ it->second->abort();
+ mSessions.erase(it);
+ }
+ }
+
+ void clearSessions() {
+ hal()->clearSessions();
+ std::lock_guard<std::mutex> lock(mSessionMutex);
+ mSessions.clear();
+ }
+
private:
+ std::mutex mSessionMutex;
const std::unique_ptr<vibrator::ManagerHalController> mHal;
+ std::unordered_map<jlong, std::shared_ptr<IVibrationSession>> mSessions
+ GUARDED_BY(mSessionMutex);
const jobject mCallbackListener;
};
@@ -142,7 +201,7 @@ static jboolean nativeTriggerSynced(JNIEnv* env, jclass /* clazz */, jlong servi
ALOGE("nativeTriggerSynced failed because native service was not initialized");
return JNI_FALSE;
}
- auto callback = service->createCallback(vibrationId);
+ auto callback = service->createSyncedVibrationCallback(vibrationId);
return service->hal()->triggerSynced(callback).isOk() ? JNI_TRUE : JNI_FALSE;
}
@@ -156,8 +215,47 @@ static void nativeCancelSynced(JNIEnv* env, jclass /* clazz */, jlong servicePtr
service->hal()->cancelSynced();
}
+static jboolean nativeStartSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+ jlong sessionId, jintArray vibratorIds) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeStartSession failed because native service was not initialized");
+ return JNI_FALSE;
+ }
+ jsize size = env->GetArrayLength(vibratorIds);
+ std::vector<int32_t> ids(size);
+ env->GetIntArrayRegion(vibratorIds, 0, size, reinterpret_cast<jint*>(ids.data()));
+ return service->startSession(sessionId, ids) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void nativeEndSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong sessionId,
+ jboolean shouldAbort) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeEndSession failed because native service was not initialized");
+ return;
+ }
+ if (shouldAbort) {
+ service->abortSession(sessionId);
+ } else {
+ service->closeSession(sessionId);
+ }
+}
+
+static void nativeClearSessions(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeClearSessions failed because native service was not initialized");
+ return;
+ }
+ service->clearSessions();
+}
+
inline static constexpr auto sNativeInitMethodSignature =
- "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+ "(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)J";
static const JNINativeMethod method_table[] = {
{"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
@@ -167,15 +265,20 @@ static const JNINativeMethod method_table[] = {
{"nativePrepareSynced", "(J[I)Z", (void*)nativePrepareSynced},
{"nativeTriggerSynced", "(JJ)Z", (void*)nativeTriggerSynced},
{"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
+ {"nativeStartSession", "(JJ[I)Z", (void*)nativeStartSession},
+ {"nativeEndSession", "(JJZ)V", (void*)nativeEndSession},
+ {"nativeClearSessions", "(J)V", (void*)nativeClearSessions},
};
int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
sJvm = jvm;
auto listenerClassName =
- "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
+ "com/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks";
jclass listenerClass = FindClassOrDie(env, listenerClassName);
- sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
-
+ sMethodIdOnSyncedVibrationComplete =
+ GetMethodIDOrDie(env, listenerClass, "onSyncedVibrationComplete", "(J)V");
+ sMethodIdOnVibrationSessionComplete =
+ GetMethodIDOrDie(env, listenerClass, "onVibrationSessionComplete", "(J)V");
return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
method_table, NELEM(method_table));
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3805c02d1bb9..9759772ae8bd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -119,7 +119,6 @@ import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accounts.AccountManagerService;
-import com.android.server.adaptiveauth.AdaptiveAuthService;
import com.android.server.adb.AdbService;
import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
@@ -205,6 +204,7 @@ import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.os.instrumentation.DynamicInstrumentationManagerService;
import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
@@ -249,6 +249,7 @@ import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService;
import com.android.server.security.advancedprotection.AdvancedProtectionService;
import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.selinux.SelinuxAuditLogsService;
@@ -2650,8 +2651,8 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
- t.traceBegin("StartAdaptiveAuthService");
- mSystemServiceManager.startService(AdaptiveAuthService.class);
+ t.traceBegin("StartAdaptiveAuthenticationService");
+ mSystemServiceManager.startService(AdaptiveAuthenticationService.class);
t.traceEnd();
}
@@ -2890,6 +2891,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(TracingServiceProxy.class);
t.traceEnd();
+ // UprobeStats DynamicInstrumentationManager
+ if (com.android.art.flags.Flags.executableMethodFileOffsets()) {
+ t.traceBegin("StartDynamicInstrumentationManager");
+ mSystemServiceManager.startService(DynamicInstrumentationManagerService.class);
+ t.traceEnd();
+ }
+
// It is now time to start up the app processes...
t.traceBegin("MakeLockSettingsServiceReady");
diff --git a/services/proguard.flags b/services/proguard.flags
index cdd41abf6c7c..977bd19a7236 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -82,7 +82,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$VibratorManagerNativeCallbacks { *; }
-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
*** *FromNative(...);
}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
new file mode 100644
index 000000000000..2c2e5fdb68d9
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_system_performance",
+}
+
+android_test {
+ name: "DynamicInstrumentationManagerServiceTests",
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "hamcrest-library",
+ "platform-test-annotations",
+ "services.core",
+ "testables",
+ "truth",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ ],
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
new file mode 100644
index 000000000000..4913d706a72d
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.os.instrumentation" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.os.instrumentation"
+ android:label="DynamicInstrumentationmanagerService Unit Tests"/>
+</manifest> \ No newline at end of file
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
new file mode 100644
index 000000000000..33defed0eae6
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "DynamicInstrumentationManagerServiceTests"
+ }
+ ]
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
new file mode 100644
index 000000000000..04073fab2059
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public abstract class TestAbstractClass {
+ abstract void abstractMethod();
+
+ void concreteMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
new file mode 100644
index 000000000000..2c25e7a52f73
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
@@ -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.server;
+
+public class TestAbstractClassImpl extends TestAbstractClass {
+ @Override
+ void abstractMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
new file mode 100644
index 000000000000..085f5953f0e5
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestClass {
+ void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g,
+ short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) {
+ }
+
+ void classParams(String a, String[] b) {
+ }
+
+ private void privateMethod() {
+ }
+
+ /**
+ * docs!
+ */
+ public void publicMethod() {
+ }
+
+ private static class InnerClass {
+ private void innerMethod(InnerClass arg, InnerClass[] argArray) {
+ }
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
new file mode 100644
index 000000000000..7af4f254ab1c
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * docs!
+ */
+public interface TestInterface {
+ /**
+ * docs!
+ */
+ void interfaceMethod();
+
+ /**
+ * docs!
+ */
+ default void defaultMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
new file mode 100644
index 000000000000..53aecbc08939
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
@@ -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.server;
+
+public class TestInterfaceImpl implements TestInterface {
+ @Override
+ public void interfaceMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
new file mode 100644
index 000000000000..5492ba6b9dd1
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.os.instrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.os.instrumentation.MethodDescriptor;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.TestAbstractClass;
+import com.android.server.TestAbstractClassImpl;
+import com.android.server.TestClass;
+import com.android.server.TestInterface;
+import com.android.server.TestInterfaceImpl;
+
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Test class for
+ * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader,
+ * MethodDescriptor)}.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest
+ */
+@Presubmit
+@SmallTest
+public class ParseMethodDescriptorTest {
+ private static final String[] PRIMITIVE_PARAMS = new String[]{
+ "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int",
+ "int[]", "long", "long[]", "float", "float[]", "double", "double[]"};
+ private static final String[] CLASS_PARAMS =
+ new String[]{"java.lang.String", "java.lang.String[]"};
+
+ @Test
+ public void primitiveParams() {
+ assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ PRIMITIVE_PARAMS));
+ }
+
+ @Test
+ public void classParams() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS));
+ }
+
+ @Test
+ public void publicMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "publicMethod"));
+ }
+
+ @Test
+ public void privateMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "privateMethod"));
+ }
+
+ @Test
+ public void innerClass() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod",
+ new String[]{TestClass.class.getName() + "$InnerClass",
+ TestClass.class.getName() + "$InnerClass[]"}));
+ }
+
+ @Test
+ public void interface_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod"));
+ }
+
+ @Test
+ public void interface_defaultMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod"));
+ }
+
+ @Test
+ public void abstractClassImpl_abstractMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod"));
+ }
+
+ @Test
+ public void abstractClass_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod"));
+ }
+
+ @Test
+ public void notFound_illegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ new String[]{"int"}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName) {
+ return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, new String[]{}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
+ return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, fqParameters));
+ }
+
+ private MethodDescriptor getMethodDescriptor(String fqcn, String methodName,
+ String[] fqParameters) {
+ MethodDescriptor methodDescriptor = new MethodDescriptor();
+ methodDescriptor.fullyQualifiedClassName = fqcn;
+ methodDescriptor.methodName = methodName;
+ methodDescriptor.fullyQualifiedParameters = fqParameters;
+ return methodDescriptor;
+ }
+
+
+}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 37a34eeb9724..205ff058275a 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,7 +29,6 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USB" />
- <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
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 c741cae1c135..80e5ee39c13d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -19,13 +19,16 @@ package com.android.server.display;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -96,6 +99,7 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -111,11 +115,13 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserManager;
+import android.os.test.FakePermissionEnforcer;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -251,6 +257,8 @@ public class DisplayManagerServiceTest {
private int[] mAllowedHdrOutputTypes;
+ private final FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer();
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -428,6 +436,13 @@ public class DisplayManagerServiceTest {
when(mContext.getResources()).thenReturn(mResources);
mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
+ mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
+ mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
+ doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+ eq(PermissionEnforcer.class));
+ doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+ eq(Context.PERMISSION_ENFORCER_SERVICE));
+
VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
@@ -3667,6 +3682,87 @@ public class DisplayManagerServiceTest {
verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid);
}
+ @Test
+ public void testGetDisplayTopology() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+ assertNotNull(topology);
+ DisplayTopology.TreeNode display = topology.getRoot();
+ assertNotNull(display);
+ assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId());
+ }
+
+ @Test
+ public void testGetDisplayTopology_NullIfFlagDisabled() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+ assertNull(topology);
+ }
+
+ @Test
+ public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ assertThrows(SecurityException.class, displayManagerBinderService::getDisplayTopology);
+ }
+
+ @Test
+ public void testSetDisplayTopology() {
+ manageDisplaysPermission(/* granted= */ true);
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+ }
+
+ @Test
+ public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
+ when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ assertThrows(SecurityException.class,
+ () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -3850,6 +3946,10 @@ public class DisplayManagerServiceTest {
DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
displayDeviceInfo.modeId = modeId;
+ if (modeId > 0 && modeId <= displayDeviceInfo.supportedModes.length) {
+ displayDeviceInfo.renderFrameRate =
+ displayDeviceInfo.supportedModes[modeId - 1].getRefreshRate();
+ }
updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
}
@@ -4017,9 +4117,11 @@ public class DisplayManagerServiceTest {
private void manageDisplaysPermission(boolean granted) {
if (granted) {
doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+ mPermissionEnforcer.grant(MANAGE_DISPLAYS);
} else {
doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext)
.enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+ mPermissionEnforcer.revoke(MANAGE_DISPLAYS);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 85e73561cf59..a2d2a81b20b4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -16,6 +16,7 @@
package com.android.server.display
+import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.view.Display
import android.view.DisplayInfo
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
deleted file mode 100644
index cd8c26d0d337..000000000000
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display
-
-import android.view.Display
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-class DisplayTopologyTest {
- private val topology = DisplayTopology()
-
- @Test
- fun addOneDisplay() {
- val displayId = 1
- val width = 800f
- val height = 600f
-
- topology.addDisplay(displayId, width, height)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId)
-
- val display = topology.mRoot!!
- assertThat(display.mDisplayId).isEqualTo(displayId)
- assertThat(display.mWidth).isEqualTo(width)
- assertThat(display.mHeight).isEqualTo(height)
- assertThat(display.mChildren).isEmpty()
- }
-
- @Test
- fun addTwoDisplays() {
- val displayId1 = 1
- val width1 = 800f
- val height1 = 600f
-
- val displayId2 = 2
- val width2 = 1000f
- val height2 = 1500f
-
- topology.addDisplay(displayId1, width1, height1)
- topology.addDisplay(displayId2, width2, height2)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
- val display1 = topology.mRoot!!
- assertThat(display1.mDisplayId).isEqualTo(displayId1)
- assertThat(display1.mWidth).isEqualTo(width1)
- assertThat(display1.mHeight).isEqualTo(height1)
- assertThat(display1.mChildren).hasSize(1)
-
- val display2 = display1.mChildren[0]
- assertThat(display2.mDisplayId).isEqualTo(displayId2)
- assertThat(display2.mWidth).isEqualTo(width2)
- assertThat(display2.mHeight).isEqualTo(height2)
- assertThat(display2.mChildren).isEmpty()
- assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
- assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
- }
-
- @Test
- fun addManyDisplays() {
- val displayId1 = 1
- val width1 = 800f
- val height1 = 600f
-
- val displayId2 = 2
- val width2 = 1000f
- val height2 = 1500f
-
- topology.addDisplay(displayId1, width1, height1)
- topology.addDisplay(displayId2, width2, height2)
-
- val noOfDisplays = 30
- for (i in 3..noOfDisplays) {
- topology.addDisplay(/* displayId= */ i, width1, height1)
- }
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
- val display1 = topology.mRoot!!
- assertThat(display1.mDisplayId).isEqualTo(displayId1)
- assertThat(display1.mWidth).isEqualTo(width1)
- assertThat(display1.mHeight).isEqualTo(height1)
- assertThat(display1.mChildren).hasSize(1)
-
- val display2 = display1.mChildren[0]
- assertThat(display2.mDisplayId).isEqualTo(displayId2)
- assertThat(display2.mWidth).isEqualTo(width2)
- assertThat(display2.mHeight).isEqualTo(height2)
- assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
- assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
- var display = display2
- for (i in 3..noOfDisplays) {
- display = display.mChildren[0]
- assertThat(display.mDisplayId).isEqualTo(i)
- assertThat(display.mWidth).isEqualTo(width1)
- assertThat(display.mHeight).isEqualTo(height1)
- // The last display should have no children
- assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(display.mOffset).isEqualTo(0)
- }
- }
-
- @Test
- fun removeDisplays() {
- val displayId1 = 1
- val width1 = 800f
- val height1 = 600f
-
- val displayId2 = 2
- val width2 = 1000f
- val height2 = 1500f
-
- topology.addDisplay(displayId1, width1, height1)
- topology.addDisplay(displayId2, width2, height2)
-
- val noOfDisplays = 30
- for (i in 3..noOfDisplays) {
- topology.addDisplay(/* displayId= */ i, width1, height1)
- }
-
- var removedDisplays = arrayOf(20)
- topology.removeDisplay(20)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
- var display1 = topology.mRoot!!
- assertThat(display1.mDisplayId).isEqualTo(displayId1)
- assertThat(display1.mWidth).isEqualTo(width1)
- assertThat(display1.mHeight).isEqualTo(height1)
- assertThat(display1.mChildren).hasSize(1)
-
- var display2 = display1.mChildren[0]
- assertThat(display2.mDisplayId).isEqualTo(displayId2)
- assertThat(display2.mWidth).isEqualTo(width2)
- assertThat(display2.mHeight).isEqualTo(height2)
- assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
- assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
- var display = display2
- for (i in 3..noOfDisplays) {
- if (i in removedDisplays) {
- continue
- }
- display = display.mChildren[0]
- assertThat(display.mDisplayId).isEqualTo(i)
- assertThat(display.mWidth).isEqualTo(width1)
- assertThat(display.mHeight).isEqualTo(height1)
- // The last display should have no children
- assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(display.mOffset).isEqualTo(0)
- }
-
- topology.removeDisplay(22)
- removedDisplays += 22
- topology.removeDisplay(23)
- removedDisplays += 23
- topology.removeDisplay(25)
- removedDisplays += 25
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
- display1 = topology.mRoot!!
- assertThat(display1.mDisplayId).isEqualTo(displayId1)
- assertThat(display1.mWidth).isEqualTo(width1)
- assertThat(display1.mHeight).isEqualTo(height1)
- assertThat(display1.mChildren).hasSize(1)
-
- display2 = display1.mChildren[0]
- assertThat(display2.mDisplayId).isEqualTo(displayId2)
- assertThat(display2.mWidth).isEqualTo(width2)
- assertThat(display2.mHeight).isEqualTo(height2)
- assertThat(display2.mChildren).hasSize(1)
- assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
- assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
- display = display2
- for (i in 3..noOfDisplays) {
- if (i in removedDisplays) {
- continue
- }
- display = display.mChildren[0]
- assertThat(display.mDisplayId).isEqualTo(i)
- assertThat(display.mWidth).isEqualTo(width1)
- assertThat(display.mHeight).isEqualTo(height1)
- // The last display should have no children
- assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
- assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(display.mOffset).isEqualTo(0)
- }
- }
-
- @Test
- fun removeAllDisplays() {
- val displayId = 1
- val width = 800f
- val height = 600f
-
- topology.addDisplay(displayId, width, height)
- topology.removeDisplay(displayId)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
- assertThat(topology.mRoot).isNull()
- }
-
- @Test
- fun removeDisplayThatDoesNotExist() {
- val displayId = 1
- val width = 800f
- val height = 600f
-
- topology.addDisplay(displayId, width, height)
- topology.removeDisplay(3)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId)
-
- val display = topology.mRoot!!
- assertThat(display.mDisplayId).isEqualTo(displayId)
- assertThat(display.mWidth).isEqualTo(width)
- assertThat(display.mHeight).isEqualTo(height)
- assertThat(display.mChildren).isEmpty()
- }
-
- @Test
- fun removePrimaryDisplay() {
- val displayId1 = 1
- val displayId2 = 2
- val width = 800f
- val height = 600f
-
- topology.addDisplay(displayId1, width, height)
- topology.addDisplay(displayId2, width, height)
- topology.mPrimaryDisplayId = displayId2
- topology.removeDisplay(displayId2)
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
- val display = topology.mRoot!!
- assertThat(display.mDisplayId).isEqualTo(displayId1)
- assertThat(display.mWidth).isEqualTo(width)
- assertThat(display.mHeight).isEqualTo(height)
- assertThat(display.mChildren).isEmpty()
- }
-
- @Test
- fun normalization_noOverlaps_leavesTopologyUnchanged() {
- val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
- /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
- topology.mRoot = display1
-
- val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
- display1.mChildren.add(display2)
-
- val primaryDisplayId = 3
- val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
- display1.mChildren.add(display3)
- topology.mPrimaryDisplayId = primaryDisplayId
-
- val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
- /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
- display2.mChildren.add(display4)
-
- topology.normalize()
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
- val actualDisplay1 = topology.mRoot!!
- assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
- assertThat(actualDisplay1.mWidth).isEqualTo(200f)
- assertThat(actualDisplay1.mHeight).isEqualTo(600f)
- assertThat(actualDisplay1.mChildren).hasSize(2)
-
- val actualDisplay2 = actualDisplay1.mChildren[0]
- assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
- assertThat(actualDisplay2.mWidth).isEqualTo(600f)
- assertThat(actualDisplay2.mHeight).isEqualTo(200f)
- assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.mOffset).isEqualTo(0f)
- assertThat(actualDisplay2.mChildren).hasSize(1)
-
- val actualDisplay3 = actualDisplay1.mChildren[1]
- assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
- assertThat(actualDisplay3.mWidth).isEqualTo(600f)
- assertThat(actualDisplay3.mHeight).isEqualTo(200f)
- assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.mOffset).isEqualTo(400f)
- assertThat(actualDisplay3.mChildren).isEmpty()
-
- val actualDisplay4 = actualDisplay2.mChildren[0]
- assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
- assertThat(actualDisplay4.mWidth).isEqualTo(200f)
- assertThat(actualDisplay4.mHeight).isEqualTo(600f)
- assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.mOffset).isEqualTo(0f)
- assertThat(actualDisplay4.mChildren).isEmpty()
- }
-
- @Test
- fun normalization_moveDisplayWithoutReparenting() {
- val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
- /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
- topology.mRoot = display1
-
- val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
- /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
- display1.mChildren.add(display2)
-
- val primaryDisplayId = 3
- val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
- display1.mChildren.add(display3)
- topology.mPrimaryDisplayId = primaryDisplayId
-
- val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
- /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
- display2.mChildren.add(display4)
-
- // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
- topology.normalize()
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
- val actualDisplay1 = topology.mRoot!!
- assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
- assertThat(actualDisplay1.mWidth).isEqualTo(200f)
- assertThat(actualDisplay1.mHeight).isEqualTo(600f)
- assertThat(actualDisplay1.mChildren).hasSize(1)
-
- val actualDisplay2 = actualDisplay1.mChildren[0]
- assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
- assertThat(actualDisplay2.mWidth).isEqualTo(200f)
- assertThat(actualDisplay2.mHeight).isEqualTo(600f)
- assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.mOffset).isEqualTo(0f)
- assertThat(actualDisplay2.mChildren).hasSize(2)
-
- val actualDisplay3 = actualDisplay2.mChildren[1]
- assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
- assertThat(actualDisplay3.mWidth).isEqualTo(600f)
- assertThat(actualDisplay3.mHeight).isEqualTo(200f)
- assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.mOffset).isEqualTo(10f)
- assertThat(actualDisplay3.mChildren).isEmpty()
-
- val actualDisplay4 = actualDisplay2.mChildren[0]
- assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
- assertThat(actualDisplay4.mWidth).isEqualTo(200f)
- assertThat(actualDisplay4.mHeight).isEqualTo(600f)
- assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.mOffset).isEqualTo(210f)
- assertThat(actualDisplay4.mChildren).isEmpty()
- }
-
- @Test
- fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
- val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
- /* height= */ 50f, /* position= */ null, /* offset= */ 0f)
- topology.mRoot = display1
-
- val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
- display1.mChildren.add(display2)
-
- val primaryDisplayId = 3
- val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
- display1.mChildren.add(display3)
- topology.mPrimaryDisplayId = primaryDisplayId
-
- // Display 3 gets moved and its left side is still on the same line as the right side
- // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
- // becomes its new parent.
- topology.normalize()
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
- val actualDisplay1 = topology.mRoot!!
- assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
- assertThat(actualDisplay1.mWidth).isEqualTo(200f)
- assertThat(actualDisplay1.mHeight).isEqualTo(50f)
- assertThat(actualDisplay1.mChildren).hasSize(1)
-
- val actualDisplay2 = actualDisplay1.mChildren[0]
- assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
- assertThat(actualDisplay2.mWidth).isEqualTo(600f)
- assertThat(actualDisplay2.mHeight).isEqualTo(200f)
- assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.mOffset).isEqualTo(0f)
- assertThat(actualDisplay2.mChildren).hasSize(1)
-
- val actualDisplay3 = actualDisplay2.mChildren[0]
- assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
- assertThat(actualDisplay3.mWidth).isEqualTo(600f)
- assertThat(actualDisplay3.mHeight).isEqualTo(200f)
- assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM)
- assertThat(actualDisplay3.mOffset).isEqualTo(0f)
- assertThat(actualDisplay3.mChildren).isEmpty()
- }
-
- @Test
- fun normalization_moveAndReparentDisplay() {
- val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
- /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
- topology.mRoot = display1
-
- val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
- /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
- display1.mChildren.add(display2)
-
- val primaryDisplayId = 3
- val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
- /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
- display1.mChildren.add(display3)
- topology.mPrimaryDisplayId = primaryDisplayId
-
- val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
- /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
- display2.mChildren.add(display4)
-
- topology.normalize()
-
- assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
- val actualDisplay1 = topology.mRoot!!
- assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
- assertThat(actualDisplay1.mWidth).isEqualTo(200f)
- assertThat(actualDisplay1.mHeight).isEqualTo(600f)
- assertThat(actualDisplay1.mChildren).hasSize(1)
-
- val actualDisplay2 = actualDisplay1.mChildren[0]
- assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
- assertThat(actualDisplay2.mWidth).isEqualTo(200f)
- assertThat(actualDisplay2.mHeight).isEqualTo(600f)
- assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay2.mOffset).isEqualTo(0f)
- assertThat(actualDisplay2.mChildren).hasSize(1)
-
- val actualDisplay3 = actualDisplay2.mChildren[0]
- assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
- assertThat(actualDisplay3.mWidth).isEqualTo(600f)
- assertThat(actualDisplay3.mHeight).isEqualTo(200f)
- assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay3.mOffset).isEqualTo(400f)
- assertThat(actualDisplay3.mChildren).hasSize(1)
-
- val actualDisplay4 = actualDisplay3.mChildren[0]
- assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
- assertThat(actualDisplay4.mWidth).isEqualTo(200f)
- assertThat(actualDisplay4.mHeight).isEqualTo(600f)
- assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
- assertThat(actualDisplay4.mOffset).isEqualTo(-400f)
- assertThat(actualDisplay4.mChildren).isEmpty()
- }
-} \ No newline at end of file
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index be698b2673ad..979384c6b2db 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -134,6 +134,7 @@ android_ravenwood_test {
"androidx.annotation_annotation",
"androidx.test.rules",
"services.core",
+ "servicestests-utils-mockito-extended",
],
srcs: [
"src/com/android/server/am/BroadcastRecordTest.java",
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 d602660597ff..a1a8b0ec7d2f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -41,6 +41,7 @@ import android.os.TestLooperManager;
import android.os.UserHandle;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.SparseArray;
@@ -97,6 +98,9 @@ public abstract class BaseBroadcastQueueTest {
.spyStatic(ProcessList.class)
.build();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -112,6 +116,8 @@ public abstract class BaseBroadcastQueueTest {
AlarmManagerInternal mAlarmManagerInt;
@Mock
ProcessList mProcessList;
+ @Mock
+ PlatformCompat mPlatformCompat;
@Mock
AppStartInfoTracker mAppStartInfoTracker;
@@ -178,6 +184,11 @@ public abstract class BaseBroadcastQueueTest {
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+ doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
}
public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 100b54897573..1481085c5f71 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
@@ -49,7 +50,6 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -65,7 +65,6 @@ import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.BundleMerger;
@@ -73,6 +72,8 @@ import android.os.DropBoxManager;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -182,10 +183,6 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
return mock(Intent.class);
}
- private static ResolveInfo makeMockManifestReceiver() {
- return mock(ResolveInfo.class);
- }
-
private static BroadcastFilter makeMockRegisteredReceiver() {
return mock(BroadcastFilter.class);
}
@@ -214,7 +211,8 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, TEST_UID, false,
null, null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
- BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
@@ -646,7 +644,8 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
@Test
public void testRunnableAt_Cached_Manifest() {
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null,
- List.of(makeMockManifestReceiver()), null, false), REASON_CONTAINS_MANIFEST);
+ List.of(makeManifestReceiver(PACKAGE_RED, CLASS_RED)), null, false),
+ REASON_CONTAINS_MANIFEST);
}
@Test
@@ -679,6 +678,19 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_ALARM);
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testRunnableAt_Cached_Prioritized_NonDeferrable_flagDisabled() {
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, null, false), REASON_CONTAINS_PRIORITIZED);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testRunnableAt_Cached_Prioritized_NonDeferrable() {
final List receivers = List.of(
@@ -687,6 +699,32 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
final BroadcastOptions options = BroadcastOptions.makeBasic()
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, null, false), REASON_CONTAINS_MANIFEST);
+ }
+
+ @Test
+ public void testRunnableAt_Cached_Ordered_NonDeferrable() {
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
+ receivers, mock(IIntentReceiver.class), true), REASON_CONTAINS_ORDERED);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+ eq(getUidForPackage(PACKAGE_GREEN)));
+ final List receivers = List.of(
+ withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10));
+ final BroadcastOptions options = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
+ doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
receivers, null, false), REASON_CONTAINS_PRIORITIZED);
}
@@ -1136,6 +1174,63 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(blueQueue, List.of(screenOn));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_prioritized_diffReceivers_flagDisabled() {
+ final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+ final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+ final BroadcastOptions screenOnOffOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("screenOnOff", Intent.ACTION_SCREEN_ON);
+
+ final Object greenReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10);
+ final Object redReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED), 5);
+ final Object blueReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ final BroadcastProcessQueue blueQueue = mImpl.getProcessQueue(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyPendingRecords(greenQueue, List.of(screenOff));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff));
+
+ assertTrue(greenQueue.isEmpty());
+ assertTrue(redQueue.isEmpty());
+ assertTrue(blueQueue.isEmpty());
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers_flagDisabled");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
@Test
public void testDeliveryGroupPolicy_prioritized_diffReceivers() {
final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
@@ -1173,6 +1268,65 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
List.of(greenReceiver, redReceiver, blueReceiver), false));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+
+ final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false);
+ screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+ "testDeliveryGroupPolicy_prioritized_diffReceivers");
+ mImpl.enqueueBroadcastLocked(screenOffRecord);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ verifyPendingRecords(greenQueue, List.of(screenOn));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOn));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+ eq(getUidForPackage(PACKAGE_GREEN)));
+
+ final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+ final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+ final BroadcastOptions screenOnOffOptions = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey("screenOnOff", Intent.ACTION_SCREEN_ON);
+
+ final Object greenReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10);
+ final Object redReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_RED, CLASS_RED), 5);
+ final Object blueReceiver = withPriority(
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0);
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ final BroadcastProcessQueue blueQueue = mImpl.getProcessQueue(PACKAGE_BLUE,
+ getUidForPackage(PACKAGE_BLUE));
+ verifyPendingRecords(greenQueue, List.of(screenOff));
+ verifyPendingRecords(redQueue, List.of(screenOff));
+ verifyPendingRecords(blueQueue, List.of(screenOff));
+
+ assertTrue(greenQueue.isEmpty());
+ assertTrue(redQueue.isEmpty());
+ assertTrue(blueQueue.isEmpty());
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, screenOnOffOptions,
+ List.of(greenReceiver, redReceiver, blueReceiver), false));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+ List.of(greenReceiver, blueReceiver), false));
verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
verifyPendingRecords(redQueue, List.of(screenOff));
verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
@@ -1569,8 +1723,9 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
verifyPendingRecords(redQueue, List.of(userPresent, timeTick));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
- public void testDeliveryDeferredForCached() throws Exception {
+ public void testDeliveryDeferredForCached_flagDisabled() throws Exception {
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
@@ -1664,8 +1819,217 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
}, false /* andRemove */);
}
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+ eq(getUidForPackage(PACKAGE_GREEN)));
+
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testDeliveryDeferredForCached_withInfiniteDeferred_flagDisabled() throws Exception {
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, optionsTimeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastOptions optionsPackageChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ optionsPackageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
- public void testDeliveryDeferredForCached_withInfiniteDeferred() throws Exception {
+ public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
+ throws Exception {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+ eq(getUidForPackage(PACKAGE_GREEN)));
final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
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 3aaf2e5c61a6..eab5ce3cee2f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -21,6 +21,7 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
@@ -45,7 +46,6 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -77,6 +77,8 @@ import android.os.IBinder;
import android.os.PowerExemptionManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArrayMap;
import android.util.Log;
@@ -446,7 +448,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo,
Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId,
- BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
+ BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private void assertHealth() {
@@ -1495,7 +1498,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, null,
Activity.RESULT_OK, null, null, false, false, false, UserHandle.USER_SYSTEM,
- backgroundStartPrivileges, false, null, PROCESS_STATE_UNKNOWN);
+ backgroundStartPrivileges, false, null, PROCESS_STATE_UNKNOWN, mPlatformCompat);
enqueueBroadcast(r);
waitForIdle();
@@ -1550,8 +1553,9 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
/**
* Verify that when dispatching we respect tranches of priority.
*/
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
- public void testPriority() throws Exception {
+ public void testPriority_flagDisabled() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -1594,6 +1598,104 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
/**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @Test
+ public void testOrdered_withPriorities() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ // Enqueue a normal broadcast that will go to several processes, and
+ // then enqueue a foreground broadcast that risks reordering
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final IIntentReceiver orderedResultTo = mock(IIntentReceiver.class);
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10)),
+ orderedResultTo, null));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
+ waitForIdle();
+
+ // Ignore the final foreground broadcast
+ mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+ assertEquals(6, mScheduledBroadcasts.size());
+
+ // We're only concerned about enforcing ordering between tranches;
+ // within a tranche we're okay with reordering
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverGreenApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0)));
+ }
+
+ /**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testPriority_changeIdDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+
+ // Enqueue a normal broadcast that will go to several processes, and
+ // then enqueue a foreground broadcast that risks reordering
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
+ waitForIdle();
+
+ // Ignore the final foreground broadcast
+ mScheduledBroadcasts.remove(makeScheduledBroadcast(receiverBlueApp, airplane));
+ assertEquals(5, mScheduledBroadcasts.size());
+
+ // We're only concerned about enforcing ordering between tranches;
+ // within a tranche we're okay with reordering
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverGreenApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverBlueApp, timezone),
+ makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0),
+ mScheduledBroadcasts.remove(0)));
+ assertEquals(
+ Set.of(makeScheduledBroadcast(receiverYellowApp, timezone)),
+ Set.of(mScheduledBroadcasts.remove(0)));
+ }
+
+ /**
* Verify prioritized receivers work as expected with deferrable broadcast - broadcast to
* app in cached state should be deferred and the rest should be delivered as per the priority
* order.
@@ -2305,8 +2407,35 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
.isLessThan(getReceiverScheduledTime(timeTickRecord, receiverBlue));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testPrioritizedBroadcastDelivery_uidForeground_flagDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
+ final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen));
+
+ enqueueBroadcast(prioritizedRecord);
+
+ waitForIdle();
+ // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
+ // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
+ assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
+ .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
+ }
+
@Test
- public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
+ public void testOrderedBroadcastDelivery_uidForeground() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2319,6 +2448,37 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
+ final IIntentReceiver resultTo = mock(IIntentReceiver.class);
+ final BroadcastRecord prioritizedRecord = makeOrderedBroadcastRecord(timeTick, callerApp,
+ List.of(receiverBlue, receiverGreen), resultTo, null);
+
+ enqueueBroadcast(prioritizedRecord);
+
+ waitForIdle();
+ // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast.
+ // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp.
+ assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen))
+ .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testPrioritizedBroadcastDelivery_uidForeground_changeIdDisabled() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+
+ mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
+ ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5);
final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp,
List.of(receiverBlue, receiverGreen));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 8cd0da721364..4a370a3cc431 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -18,6 +18,8 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.am.BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE;
import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
@@ -33,6 +35,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
@@ -46,11 +50,17 @@ import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.SubscriptionManager;
import androidx.test.filters.SmallTest;
+import com.android.server.compat.PlatformCompat;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -73,6 +83,9 @@ import java.util.function.BiFunction;
public class BroadcastRecordTest {
private static final String TAG = "BroadcastRecordTest";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int USER0 = UserHandle.USER_SYSTEM;
private static final String PACKAGE1 = "pkg1";
private static final String PACKAGE2 = "pkg2";
@@ -89,10 +102,14 @@ public class BroadcastRecordTest {
@Mock BroadcastQueue mQueue;
@Mock ProcessRecord mProcess;
+ @Mock PlatformCompat mPlatformCompat;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
}
@Test
@@ -108,13 +125,13 @@ public class BroadcastRecordTest {
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), 0)), false, mPlatformCompat));
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), -10)), false, mPlatformCompat));
assertArrayEquals(new int[] {-1},
calculateBlockedUntilBeyondCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
+ createResolveInfo(PACKAGE1, getAppId(1), 10)), false, mPlatformCompat));
}
@Test
@@ -128,18 +145,19 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
- assertArrayEquals(new int[] {-1,-1,-1},
+ assertArrayEquals(new int[] {-1, -1, -1},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
- assertArrayEquals(new int[] {-1,-1,-1},
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
- createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false, mPlatformCompat));
}
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
@Test
public void testIsPrioritized_Yes() {
assertTrue(isPrioritized(List.of(
@@ -151,18 +169,203 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 0))));
- assertArrayEquals(new int[] {0,1,2},
+ assertArrayEquals(new int[] {0, 1, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 3, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities() {
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertFalse(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {-1, -1, -1, -1, -1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 0, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 3, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1, 0},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 2, 2},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(4), 0)), false, mPlatformCompat));
+ }
+
+ @EnableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @Test
+ public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
+ assertTrue(isPrioritized(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
+
+ assertArrayEquals(new int[] {0, 1, 2},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
- assertArrayEquals(new int[] {0,0,2,3,3},
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 1, 1},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 2, 2, 2},
calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 20),
createResolveInfo(PACKAGE2, getAppId(2), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
createResolveInfo(PACKAGE3, getAppId(3), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ createResolveInfo(PACKAGE3, getAppId(4), 0)), false, mPlatformCompat));
+ assertArrayEquals(new int[] {0, 0, 1, 1, 3},
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(2), 0)), false, mPlatformCompat));
}
@Test
@@ -602,6 +805,66 @@ public class BroadcastRecordTest {
assertTrue(record3.matchesDeliveryGroup(record1));
}
+ @Test
+ public void testCalculateChangeStateForReceivers() {
+ assertArrayEquals(new boolean[] {true, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {true, true, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+ assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, true, false, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+ assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, true, false, false, false, true},
+ calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+
+ doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
+ eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+ assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ assertArrayEquals(new boolean[] {false, false, false, false, false, false},
+ calculateChangeState(
+ List.of(createResolveInfo(PACKAGE1, getAppId(1)),
+ createResolveInfo(PACKAGE3, getAppId(3)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE2, getAppId(1)),
+ createResolveInfo(PACKAGE2, getAppId(2)),
+ createResolveInfo(PACKAGE3, getAppId(3)))));
+ }
+
+ private boolean[] calculateChangeState(List<Object> receivers) {
+ return BroadcastRecord.calculateChangeStateForReceivers(receivers,
+ CHANGE_LIMIT_PRIORITY_SCOPE, mPlatformCompat);
+ }
+
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
String packageName, int userId) {
record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
@@ -753,16 +1016,17 @@ public class BroadcastRecordTest {
BackgroundStartPrivileges.NONE,
false /* timeoutExempt */,
filterExtrasForReceiver,
- PROCESS_STATE_UNKNOWN);
+ PROCESS_STATE_UNKNOWN,
+ mPlatformCompat);
}
private static int getAppId(int i) {
return Process.FIRST_APPLICATION_UID + i;
}
- private static boolean isPrioritized(List<Object> receivers) {
+ private boolean isPrioritized(List<Object> receivers) {
return BroadcastRecord.isPrioritized(
- calculateBlockedUntilBeyondCount(receivers, false), false);
+ calculateBlockedUntilBeyondCount(receivers, false, mPlatformCompat), false);
}
private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index c645c0852f1b..9b7bbe04132c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -114,6 +114,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 8164ef9314d9..f0d3456a39de 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -19,9 +19,13 @@ package com.android.server.accessibility.magnification;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -660,6 +664,90 @@ public class MagnificationControllerTest {
}
@Test
+ public void scaleMagnificationByStep_fullscreenMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 1.0f, false);
+ reset(mScreenMagnificationController);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR} and the center coordinates are
+ // unchanged (Float.NaN as values denotes unchanged center).
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR),
+ eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE), eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMaxScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale < SCALE_MAX_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isGreaterThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMinScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MAX_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale > SCALE_MIN_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isLessThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_windowedMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mMagnificationConnectionManager);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR}.
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR));
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE));
+ }
+
+ @Test
public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index 0218a7835586..000000000000
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index bc410d9168a9..88829c1a99b3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -70,8 +71,16 @@ import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
import android.os.Handler;
@@ -84,6 +93,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.security.GateKeeper;
import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
@@ -93,6 +103,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -111,6 +122,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -144,6 +156,27 @@ public class BiometricServiceTest {
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
+ private final ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback.Stub>
+ mFingerprintAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFingerprintAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback.Stub>
+ mFaceAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFaceAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<BiometricStateListener> mBiometricStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
+ private final List<FingerprintSensorPropertiesInternal>
+ mFingerprintSensorPropertiesInternals = List.of(
+ new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ private final List<FaceSensorPropertiesInternal>
+ mFaceSensorPropertiesInternals = List.of(
+ new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_UNKNOWN,
+ false /* supportsFaceDetection */, false /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */));
private BiometricService mBiometricService;
@@ -192,6 +225,10 @@ public class BiometricServiceTest {
@Mock
private BiometricNotificationLogger mNotificationLogger;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private FaceManager mFaceManager;
BiometricContextProvider mBiometricContextProvider;
@@ -1975,6 +2012,59 @@ public class BiometricServiceTest {
eq(hardwareAuthenticators));
}
+ @Test
+ public void testMandatoryBiometricsValue_whenParentProfileEnabled() throws RemoteException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final int profileParentId = context.getContentResolver().getUserId();
+ final int userId = profileParentId + 1;
+ final BiometricService.SettingObserver settingObserver =
+ new BiometricService.SettingObserver(
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
+
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorRegisteredCallbackCaptor.capture());
+
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFingerprintSensorPropertiesInternals);
+ mFaceAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFaceSensorPropertiesInternals);
+
+ verify(mFingerprintManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FINGERPRINT, true /* hasEnrollments */);
+
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FACE, true /* hasEnrollments */);
+
+ when(mUserManager.getProfileParent(userId)).thenReturn(new UserInfo(profileParentId,
+ "", 0));
+ when(mUserManager.getEnabledProfileIds(profileParentId)).thenReturn(new int[]{userId});
+
+ //Disable Identity Check for profile user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 0, userId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 0,
+ userId);
+ //Enable Identity Check for parent user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 1, profileParentId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 1,
+ profileParentId);
+
+ assertTrue(settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ userId));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index 685e8d6a3bc5..e611867493eb 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -65,7 +65,7 @@ public class ContextHubServiceTest {
new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList(""));
when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID);
when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
- when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
+ when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo);
when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index dddab657be14..5a7027edc20d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -2158,13 +2158,11 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainEnabled() throws Exception {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
@RequiresFlagsDisabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN)
public void testBackgroundChainOnProcStateChangeSameDelay() throws Exception {
// initialization calls setFirewallChainEnabled, so we want to reset the invocations.
@@ -2194,10 +2192,7 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled({
- Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE,
- Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN
- })
+ @RequiresFlagsEnabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN)
public void testBackgroundChainOnProcStateChangeDifferentDelays() throws Exception {
// The app will be blocked when there is no prior proc-state.
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
@@ -2247,7 +2242,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnAllowlistChange() throws Exception {
// initialization calls setFirewallChainEnabled, so we want to reset the invocations.
clearInvocations(mNetworkManager);
@@ -2285,7 +2279,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
// initialization calls setFirewallChainEnabled, so we want to reset the invocations.
clearInvocations(mNetworkManager);
@@ -2387,7 +2380,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
int testProcStateSeq = 0;
try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2450,7 +2442,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
final int testProcStateSeq = 51;
try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2470,7 +2461,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
int testProcStateSeq = 0;
try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2559,7 +2549,6 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testObsoleteHandleUidChanged() throws Exception {
callAndWaitOnUidGone(UID_A);
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
index d1806881ee37..154494a13072 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
-import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
+import static com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -66,12 +66,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
- * atest FrameworksServicesTests:AdaptiveAuthServiceTest
+ * atest FrameworksServicesTests:AdaptiveAuthenticationServiceTest
*/
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AdaptiveAuthServiceTest {
+public class AdaptiveAuthenticationServiceTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -81,7 +81,7 @@ public class AdaptiveAuthServiceTest {
private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason
private Context mContext;
- private AdaptiveAuthService mAdaptiveAuthService;
+ private AdaptiveAuthenticationService mAdaptiveAuthenticationService;
@Mock
LockPatternUtils mLockPatternUtils;
@@ -124,8 +124,9 @@ public class AdaptiveAuthServiceTest {
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mUserManager);
- mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils);
- mAdaptiveAuthService.init();
+ mAdaptiveAuthenticationService = new AdaptiveAuthenticationService(
+ mContext, mLockPatternUtils);
+ mAdaptiveAuthenticationService.init();
verify(mLockSettings).registerLockSettingsStateListener(
mLockSettingsStateListenerCaptor.capture());
@@ -317,13 +318,13 @@ public class AdaptiveAuthServiceTest {
private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) {
assertEquals(expectedCntFailedAttempts,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
verify(mWindowManager, never()).lockNow();
}
private void verifyLockDevice(int userId) {
assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
verify(mLockPatternUtils).requireStrongAuth(
eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId));
// If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 000000000000..bc8efa92c16f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index c9d5241c57b7..b3ec2153542a 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,7 +30,6 @@ import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.After;
@@ -42,7 +41,6 @@ import org.mockito.MockitoAnnotations;
public class UiServiceTestCase {
@Mock protected PackageManagerInternal mPmi;
- @Mock protected UserManagerInternal mUmi;
@Mock protected UriGrantsManagerInternal mUgmInternal;
protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -94,8 +92,6 @@ public class UiServiceTestCase {
}
});
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mUmi);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 0b89c11a11f4..cc0286508cdc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2291,7 +2291,9 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() {
final String pkg = "package";
final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
@@ -2366,7 +2368,9 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
public void testMoveSections_notificationBundled() {
final List<NotificationRecord> notificationList = new ArrayList<>();
final String pkg = "package";
@@ -2406,11 +2410,13 @@ public class GroupHelperTest extends UiServiceTestCase {
NotificationChannel.NEWS_ID);
for (NotificationRecord record: notificationList) {
if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
&& record.getSbn().getId() % 2 == 0) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
&& record.getSbn().getId() % 2 != 0) {
record.updateNotificationChannel(newsChannel);
mGroupHelper.onChannelUpdated(record);
@@ -2436,7 +2442,9 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
public void testCacheAndCancelAppSummary_notificationBundled() {
// check that the original app summary is canceled & cached on classification regrouping
final List<NotificationRecord> notificationList = new ArrayList<>();
@@ -2468,7 +2476,8 @@ public class GroupHelperTest extends UiServiceTestCase {
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
IMPORTANCE_DEFAULT);
for (NotificationRecord record: notificationList) {
- if (record.getChannel().getId().equals(channel1.getId())) {
+ if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2495,6 +2504,7 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() {
@@ -2525,7 +2535,8 @@ public class GroupHelperTest extends UiServiceTestCase {
BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
NotificationChannel.SOCIAL_MEDIA_ID);
for (NotificationRecord record: notificationList) {
- if (record.getOriginalGroupKey().contains("testGrp")) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2569,6 +2580,7 @@ public class GroupHelperTest extends UiServiceTestCase {
@Test
@EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() {
@@ -2623,7 +2635,8 @@ public class GroupHelperTest extends UiServiceTestCase {
BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
NotificationChannel.SOCIAL_MEDIA_ID);
for (NotificationRecord record: notificationList) {
- if (record.getOriginalGroupKey().contains("testGrp")) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2642,6 +2655,64 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testValidGroupsRegrouped_notificationBundledWhileEnqueued() {
+ // Check that valid group notifications are regrouped if classification is done
+ // before onNotificationPostedWithDelay (within DELAY_FOR_ASSISTANT_TIME)
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = 3;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ }
+
+ // Classify/bundle child notifications. Don't call onChannelUpdated,
+ // adjustments applied while enqueued will use NotificationAdjustmentExtractor.
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+
+ // Check that notifications are forced grouped and app-provided summaries are canceled
+ for (NotificationRecord record: notificationList) {
+ mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+ }
+
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(numChildren)).addAutoGroup(anyString(), eq(expectedGroupKey_social),
+ eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(),
+ anyString(), any());
+ verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
+ anyString());
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7c51a1..e5c42082ab97 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1484,7 +1484,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToUnbind.get(0).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
@Test
public void populateComponentsToBind() {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -1508,8 +1507,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ false);
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
assertEquals(2, componentsToBind.size());
assertEquals(1, componentsToBind.get(0).size());
@@ -1519,33 +1517,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
- @Test
- public void populateComponentsToBind_isVisibleBackgroundUser_addComponentsToBindButNotAddToEnabledComponent() {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
- APPROVAL_BY_COMPONENT);
-
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
- ArraySet<ComponentName> allowed = new ArraySet<>();
- allowed.add(ComponentName.unflattenFromString("pkg1/cmp1"));
- approvedComponentsByUser.put(11, allowed);
- IntArray users = new IntArray();
- users.add(11);
-
- SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
-
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ true);
-
- assertEquals(1, componentsToBind.size());
- assertEquals(1, componentsToBind.get(11).size());
- assertTrue(componentsToBind.get(11).contains(ComponentName.unflattenFromString(
- "pkg1/cmp1")));
- assertThat(service.isComponentEnabledForCurrentProfiles(
- new ComponentName("pkg1", "cmp1"))).isFalse();
- assertThat(service.isComponentEnabledForPackage("pkg1")).isFalse();
- }
-
@Test
public void testOnNullBinding() throws Exception {
Context context = mock(Context.class);
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
index c0f514fb9673..850884f84b01 100644
--- a/services/tests/vibrator/AndroidManifest.xml
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -32,6 +32,9 @@
<uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
<!-- Required to play system-only haptic feedback constants -->
<uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
+ <!-- Required to play vendor effects and start vendor sessions -->
+ <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" />
+ <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" />
<application android:debuggable="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index dfdd0cde6aba..88ba9e3af6df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -83,6 +84,8 @@ import android.os.Vibrator;
import android.os.VibratorInfo;
import android.os.test.FakeVibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
@@ -195,6 +198,7 @@ public class VibratorManagerServiceTest {
new SparseArray<>();
private final List<HalVibration> mPendingVibrations = new ArrayList<>();
+ private final List<VendorVibrationSession> mPendingSessions = new ArrayList<>();
private VibratorManagerService mService;
private Context mContextSpy;
@@ -264,6 +268,11 @@ public class VibratorManagerServiceTest {
grantPermission(android.Manifest.permission.VIBRATE);
// Cancel any pending vibration from tests, including external vibrations.
cancelVibrate(mService);
+ // End pending sessions.
+ for (VendorVibrationSession session : mPendingSessions) {
+ session.cancelSession();
+ }
+ mTestLooper.dispatchAll();
// Wait until pending vibrations end asynchronously.
for (HalVibration vibration : mPendingVibrations) {
vibration.waitForEnd();
@@ -1229,6 +1238,36 @@ public class VibratorManagerServiceTest {
.anyMatch(PrebakedSegment.class::isInstance));
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void vibrate_withOngoingHigherImportanceSession_ignoresEffect() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ HalVibration vibration = vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ // The second vibration shouldn't have played any prebaked segment.
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
+ .anyMatch(PrebakedSegment.class::isInstance));
+ }
+
@Test
public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
throws Exception {
@@ -1289,6 +1328,36 @@ public class VibratorManagerServiceTest {
.filter(PrebakedSegment.class::isInstance).count());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void vibrate_withOngoingLowerImportanceSession_cancelsOngoingSession() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ HalVibration vibration = vibrateAndWaitUntilFinished(service,
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ // One segment played is the prebaked CLICK from the new vibration.
+ assertEquals(1, mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance).count());
+ }
+
@Test
public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect()
throws Exception {
@@ -1416,16 +1485,16 @@ public class VibratorManagerServiceTest {
// The native callback will be dispatched manually in this test.
mTestLooper.stopAutoDispatchAndIgnoreExceptions();
- ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
ArgumentCaptor.forClass(
- VibratorManagerService.OnSyncedVibrationCompleteListener.class);
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
verify(mNativeWrapperMock).init(listenerCaptor.capture());
CountDownLatch triggerCountDown = new CountDownLatch(1);
// Mock trigger callback on registered listener right after the synced vibration starts.
when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
- listenerCaptor.getValue().onComplete(answer.getArgument(0));
+ listenerCaptor.getValue().onSyncedVibrationComplete(answer.getArgument(0));
triggerCountDown.countDown();
return true;
});
@@ -2318,6 +2387,34 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void onExternalVibration_withOngoingHigherImportanceSession_ignoreNewVibration()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ // External vibration is ignored.
+ assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+
+ // Session still running.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ }
+
@Test
public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
throws Exception {
@@ -2373,6 +2470,36 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
}
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @Test
+ public void onExternalVibration_withOngoingLowerImportanceSession_cancelsOngoingSession()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ mTestLooper.dispatchAll();
+
+ // Session is cancelled.
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ assertEquals(Arrays.asList(false, true),
+ mVibratorProviders.get(1).getExternalControlStates());
+ }
+
@Test
public void onExternalVibration_withRingtone_usesRingerModeSettings() {
mockVibrators(1);
@@ -2638,6 +2765,376 @@ public class VibratorManagerServiceTest {
}
@Test
+ @DisableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutFeatureFlag_throwsException() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ assertThrows("Expected starting session without feature flag to fail!",
+ UnsupportedOperationException.class,
+ () -> startSession(service, RINGTONE_ATTRS, callback, vibratorId));
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(callback, never()).onStarted(any(IVibrationSession.class));
+ verify(callback, never()).onFinishing();
+ verify(callback, never()).onFinished(anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutCapability_doesNotStart() throws Exception {
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+ callback, vibratorId);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(callback, never()).onFinishing();
+ verify(callback)
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutCallback_doesNotStart() {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ int vibratorId = 1;
+ mockVibrators(vibratorId);
+ VibratorManagerService service = createSystemReadyService();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+ /* callback= */ null, vibratorId);
+ mTestLooper.dispatchAll();
+
+ assertThat(session).isNull();
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withoutVibratorIds_doesNotStart() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ int[] nullIds = null;
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, nullIds);
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+
+ int[] emptyIds = {};
+ session = startSession(service, RINGTONE_ATTRS, callback, emptyIds);
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(callback, never()).onFinishing();
+ verify(callback, times(2))
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_badVibratorId_failsToStart() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ when(mNativeWrapperMock.startSession(anyLong(), any(int[].class))).thenReturn(false);
+ doReturn(false).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 3}));
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 2}));
+ VibratorManagerService service = createSystemReadyService();
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3}));
+ verify(callback, never()).onStarted(any(IVibrationSession.class));
+ verify(callback, never()).onFinishing();
+ verify(callback)
+ .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenFinish_returnsSuccessAfterCallback() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().finishSession();
+
+ // Session not ended until HAL callback.
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ // Dispatch HAL callbacks.
+ mTestLooper.moveTimeForward(sessionFinishDelayMs);
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenSendCancelSignal_cancelsSession() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ int sessionFinishDelayMs = 200;
+ IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ session.getCancellationSignal().cancel();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenCancel_returnsCancelStatus() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ // Delay not applied when session is aborted.
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_finishThenCancel_returnsRightAwayWithFinishedStatus()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ // Delay not applied when session is aborted.
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+
+ captor.getValue().finishSession();
+ mTestLooper.dispatchAll();
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_thenHalCancels_returnsCancelStatus()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1, 2);
+ VibratorManagerService service = createSystemReadyService();
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+ ArgumentCaptor.forClass(
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
+ verify(mNativeWrapperMock).init(listenerCaptor.capture());
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+ verify(callback).onStarted(any(IVibrationSession.class));
+
+ // Mock HAL ending session unexpectedly.
+ listenerCaptor.getValue().onVibrationSessionComplete(session.getSessionId());
+ mTestLooper.dispatchAll();
+
+ assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
+ verify(callback).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withPowerMode_usesPowerModeState() throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+ VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+ verify(callback).onStarted(captor.capture());
+ captor.getValue().cancelSession();
+ mTestLooper.dispatchAll();
+
+ mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+ VendorVibrationSession session3 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock, never())
+ .startSession(eq(session1.getSessionId()), any(int[].class));
+ verify(mNativeWrapperMock).startSession(eq(session2.getSessionId()), eq(new int[] {1}));
+ verify(mNativeWrapperMock).startSession(eq(session3.getSessionId()), eq(new int[] {1}));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingHigherImportanceVibration_ignoresSession()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
+ vibrate(service, effect, ALARM_ATTRS);
+
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2,
+ service, TEST_TIMEOUT_MILLIS));
+
+ VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+
+ verify(mNativeWrapperMock, never())
+ .startSession(eq(session.getSessionId()), any(int[].class));
+ assertThat(session.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+ verify(callback, never()).onFinishing();
+ verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_IGNORED));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingLowerImportanceVibration_cancelsOngoing()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
+ HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ vibration.waitForEnd();
+ assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS));
+ mTestLooper.dispatchAll();
+
+ assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+ assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+ verify(callback).onStarted(any(IVibrationSession.class));
+ }
+
+ @Test
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void startVibrationSession_withOngoingLowerImportanceExternalVibration_cancelsOngoing()
+ throws Exception {
+ mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ VibratorManagerService service = createSystemReadyService();
+ IVibrationSessionCallback callback =
+ mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+ IBinder firstToken = mock(IBinder.class);
+ IExternalVibrationController controller = mock(IExternalVibrationController.class);
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ AUDIO_ALARM_ATTRS,
+ controller, firstToken);
+ ExternalVibrationScale scale =
+ mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+ VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+ mTestLooper.dispatchAll();
+
+ assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+ // The external vibration should have been cancelled
+ verify(controller).mute();
+ assertEquals(Arrays.asList(false, true, false),
+ mVibratorProviders.get(1).getExternalControlStates());
+ verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+ verify(callback).onStarted(any(IVibrationSession.class));
+ }
+
+ @Test
public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -3050,6 +3547,30 @@ public class VibratorManagerServiceTest {
when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
}
+ private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) {
+ Handler handler = new Handler(mTestLooper.getLooper());
+ ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+ ArgumentCaptor.forClass(
+ VibratorManagerService.VibratorManagerNativeCallbacks.class);
+ verify(mNativeWrapperMock).init(listenerCaptor.capture());
+ doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+ doAnswer(args -> {
+ handler.postDelayed(
+ () -> listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0)),
+ delayToEndSessionMillis);
+ return null;
+ }).when(mNativeWrapperMock).endSession(anyLong(), eq(false));
+ doAnswer(args -> {
+ listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0));
+ return null;
+ }).when(mNativeWrapperMock).endSession(anyLong(), eq(true));
+
+ IBinder token = mock(IBinder.class);
+ IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+ doReturn(token).when(callback).asBinder();
+ return callback;
+ }
+
private void cancelVibrate(VibratorManagerService service) {
service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
}
@@ -3157,6 +3678,16 @@ public class VibratorManagerServiceTest {
return vib;
}
+ private VendorVibrationSession startSession(VibratorManagerService service,
+ VibrationAttributes attrs, IVibrationSessionCallback callback, int... vibratorIds) {
+ VendorVibrationSession session = service.startVendorVibrationSessionInternal(UID,
+ Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, vibratorIds, attrs, "reason", callback);
+ if (session != null) {
+ mPendingSessions.add(session);
+ }
+ return session;
+ }
+
private boolean waitUntil(Predicate<VibratorManagerService> predicate,
VibratorManagerService service, long timeout) throws InterruptedException {
long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 28ae271e20fc..cf5323e1f3a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -288,7 +288,6 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
* Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
*/
@Test
- @EnableFlags(com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
public void testTakeScreenshot_flagEnabled() {
sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
@@ -296,17 +295,6 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
}
/**
- * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
- */
- @Test
- @DisableFlags({com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE,
- com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER})
- public void testTakeScreenshot_flagDisabled() {
- sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
- mPhoneWindowManager.assertTakeScreenshotNotCalled();
- }
-
- /**
* META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
*/
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6596ee935b4b..a51ce9951ab4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -699,8 +699,8 @@ class TestPhoneWindowManager {
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mWindowWakeUpPolicy)
- .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+ verify(mWindowWakeUpPolicy).wakeUpFromKey(
+ eq(DEFAULT_DISPLAY), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 7322e5a3b681..3ca352cfa60d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -22,6 +22,7 @@ import static android.os.PowerManager.WAKE_REASON_GESTURE;
import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.KeyEvent.KEYCODE_HOME;
@@ -35,6 +36,7 @@ import static com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraL
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -43,6 +45,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -52,6 +55,8 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -125,6 +130,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -136,7 +142,8 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
verifyNoPowerManagerWakeUp();
@@ -144,12 +151,14 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -161,7 +170,7 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
verifyNoPowerManagerWakeUp();
@@ -169,7 +178,8 @@ public final class WindowWakeUpPolicyTests {
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
@@ -186,7 +196,7 @@ public final class WindowWakeUpPolicyTests {
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
}
@@ -201,11 +211,13 @@ public final class WindowWakeUpPolicyTests {
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
setDefaultDisplayState(Display.STATE_ON);
@@ -213,30 +225,69 @@ public final class WindowWakeUpPolicyTests {
setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
- mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+ mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
+ mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
config_allowTheaterModeWakeFromMotion,
WAKE_REASON_WAKE_MOTION,
"android.policy:MOTION");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that power is woken up and display isn't woken up individually
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(
+ anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
+ verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_nonPowerKey() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_HOME, true),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_WAKE_KEY,
"android.policy:KEY");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_powerKey() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -245,7 +296,8 @@ public final class WindowWakeUpPolicyTests {
// Test with power key
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, true),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
@@ -254,13 +306,31 @@ public final class WindowWakeUpPolicyTests {
// even if the power-key specific theater mode config is disabled.
setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, false),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromKey_invalidDisplay_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = Display.INVALID_DISPLAY;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromKey(
+ displayId, mClock.uptimeMillis(), KEYCODE_POWER, /* isDown= */ false);
+
+ // Verify that default display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_POWER_BUTTON),
+ eq("android.policy:POWER"), eq(DEFAULT_DISPLAY));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromLid() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromLid(),
@@ -270,6 +340,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromWakeGesture() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromWakeGesture(),
@@ -279,6 +350,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testwakeUpFromCameraCover() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -288,6 +360,7 @@ public final class WindowWakeUpPolicyTests {
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromPowerKeyCameraGesture() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index a34094ce6452..98949d0c45cf 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -68,7 +68,7 @@ public class Log {
// Used to synchronize singleton logging lazy initialization
private static final Object sSingletonSync = new Object();
private static EventManager sEventManager;
- private static SessionManager sSessionManager;
+ private static volatile SessionManager sSessionManager;
private static Object sLock = null;
/**
@@ -379,6 +379,23 @@ public class Log {
return sSessionManager;
}
+ @VisibleForTesting
+ public static SessionManager setSessionManager(Context context,
+ java.lang.Runnable cleanSessionRunnable) {
+ // Checking for null again outside of synchronization because we only need to synchronize
+ // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
+ if (sSessionManager == null) {
+ synchronized (sSingletonSync) {
+ if (sSessionManager == null) {
+ sSessionManager = new SessionManager(cleanSessionRunnable);
+ sSessionManager.setContext(context);
+ return sSessionManager;
+ }
+ }
+ }
+ return sSessionManager;
+ }
+
public static void setTag(String tag) {
TAG = tag;
DEBUG = isLoggable(android.util.Log.DEBUG);
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 00e344c67cc5..ac1e69e92ec0 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -62,9 +62,7 @@ public class SessionManager {
@VisibleForTesting
public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64);
- @VisibleForTesting
- public java.lang.Runnable mCleanStaleSessions = () ->
- cleanupStaleSessions(getSessionCleanupTimeoutMs());
+ private final java.lang.Runnable mCleanStaleSessions;
private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
// Overridden in LogTest to skip query to ContentProvider
@@ -110,29 +108,39 @@ public class SessionManager {
}
public SessionManager() {
+ mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs());
+ }
+
+ @VisibleForTesting
+ public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) {
+ mCleanStaleSessions = cleanStaleSessionsRunnable;
}
private long getSessionCleanupTimeoutMs() {
return mSessionCleanupTimeoutMs.get();
}
- private synchronized void resetStaleSessionTimer() {
+ private void resetStaleSessionTimer() {
if (!Flags.endSessionImprovements()) {
- mSessionCleanupHandler.removeCallbacksAndMessages(null);
- // Will be null in Log Testing
- if (mCleanStaleSessions != null) {
- mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
- getSessionCleanupTimeoutMs());
- }
- } else {
- if (mCleanStaleSessions != null
- && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
+ resetStaleSessionTimerOld();
+ return;
+ }
+ // Will be null in Log Testing
+ if (mCleanStaleSessions == null) return;
+ synchronized (mSessionCleanupHandler) {
+ if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
getSessionCleanupTimeoutMs());
}
}
}
+ private synchronized void resetStaleSessionTimerOld() {
+ if (mCleanStaleSessions == null) return;
+ mSessionCleanupHandler.removeCallbacksAndMessages(null);
+ mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
+ }
+
/**
* Determines whether or not to start a new session or continue an existing session based on
* the {@link Session.Info} info passed into startSession. If info is null, a new Session is
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2a06c3da0195..6490cbe3e31a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2183,8 +2183,8 @@ public class CarrierConfigManager {
* Maximum size in bytes of the PDU to send or download when connected to a non-terrestrial
* network. MmsService will return a result code of MMS_ERROR_TOO_LARGE_FOR_TRANSPORT if
* the PDU exceeds this limit when connected to a non-terrestrial network.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT =
"mms_max_ntn_payload_size_bytes_int";
@@ -9850,9 +9850,8 @@ public class CarrierConfigManager {
* manually scanning available cellular network.
* If key is {@code true}, satellite plmn should not be exposed to user and should be
* automatically set, {@code false} otherwise. Default value is {@code true}.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL =
"remove_satellite_plmn_in_manual_network_scan_bool";
@@ -9877,18 +9876,18 @@ public class CarrierConfigManager {
/**
* Doesn't support unrestricted traffic on satellite network.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0;
/**
* Support unrestricted but bandwidth_constrained traffic on satellite network.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1;
/**
* Support unrestricted satellite network that serves all traffic.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_DATA_SUPPORT_ALL = 2;
/**
* Indicates what kind of traffic an {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED}
@@ -9898,8 +9897,8 @@ public class CarrierConfigManager {
* {@link ApnSetting#INFRASTRUCTURE_SATELLITE} from APN infrastructure_bitmask, and this
* configuration is ignored.
* By default it only supports restricted data.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT =
"satellite_data_support_mode_int";
@@ -9911,8 +9910,8 @@ public class CarrierConfigManager {
* {@link com.android.ims.ImsConfig.WfcModeFeatureValueConstants#WIFI_PREFERRED}
* {@code false} - roaming preference can be changed by user independently and is not
* overridden when device is connected to non-terrestrial network.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL =
"override_wfc_roaming_mode_while_using_ntn_bool";
@@ -9945,8 +9944,8 @@ public class CarrierConfigManager {
* Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management.
* `app_name` is an optional attribute in the request and may vary depending on the carrier
* requirement.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING =
"satellite_entitlement_app_name_string";
@@ -9954,9 +9953,8 @@ public class CarrierConfigManager {
* URL to redirect user to get more information about the carrier support for satellite.
*
* The default value is empty string.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
"satellite_information_redirect_url_string";
/**
@@ -9966,9 +9964,8 @@ public class CarrierConfigManager {
* This will need agreement with carriers before enabling this flag.
*
* The default value is false.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
"emergency_messaging_supported_bool";
@@ -9983,9 +9980,8 @@ public class CarrierConfigManager {
* prompt user to switch to using satellite emergency messaging.
*
* The default value is 30 seconds.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
"emergency_call_to_satellite_t911_handover_timeout_millis_int";
@@ -9998,9 +9994,8 @@ public class CarrierConfigManager {
* The default capabilities are
* {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
* {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
"carrier_roaming_satellite_default_services_int_array";
@@ -10031,9 +10026,8 @@ public class CarrierConfigManager {
* Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite
* attachment. For more on NIDD, see 3GPP TS 29.542.
* Note this config is the only source of truth regarding the definition of the APN.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING =
"satellite_nidd_apn_name_string";
@@ -10044,9 +10038,8 @@ public class CarrierConfigManager {
*
* If {@code false}, the emergency call is always blocked if device is in emergency satellite
* mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL =
"satellite_roaming_turn_off_session_for_emergency_call_bool";
@@ -10059,14 +10052,14 @@ public class CarrierConfigManager {
/**
* Device can connect to carrier roaming non-terrestrial network automatically.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0;
/**
* Device can connect to carrier roaming non-terrestrial network only if user manually triggers
* satellite connection.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1;
/**
* Indicates carrier roaming non-terrestrial network connect type that the device can use to
@@ -10074,8 +10067,8 @@ public class CarrierConfigManager {
* If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be
* displayed to user when the device is eligible to use carrier roaming
* non-terrestrial network.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT =
"carrier_roaming_ntn_connect_type_int";
@@ -10088,7 +10081,6 @@ public class CarrierConfigManager {
* will be made to T911.
*
* The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}.
- *
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final String
@@ -10105,9 +10097,8 @@ public class CarrierConfigManager {
* After the timer is expired, device is marked as eligible for satellite communication.
*
* The default value is 180 seconds.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT =
"carrier_supported_satellite_notification_hysteresis_sec_int";
@@ -10153,7 +10144,9 @@ public class CarrierConfigManager {
"satellite_roaming_esos_inactivity_timeout_sec_int";
/**
- * A string array containing the list of messaging package names that support satellite.
+ * A string array containing the list of messaging apps that support satellite.
+ *
+ * The default value contains only "com.google.android.apps.messaging"
*
* @hide
*/
@@ -10166,9 +10159,8 @@ public class CarrierConfigManager {
* the default APN (i.e. internet) will be used for tethering.
*
* This config is only available when using Preset APN(not user edited) as Preferred APN.
- *
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL =
"disable_dun_apn_while_roaming_with_preset_apn_bool";
@@ -11313,6 +11305,8 @@ public class CarrierConfigManager {
NetworkRegistrationInfo.SERVICE_TYPE_SMS,
NetworkRegistrationInfo.SERVICE_TYPE_MMS
});
+ sDefaults.putStringArray(KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY, new String[]{
+ "com.google.android.apps.messaging"});
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
diff --git a/telephony/java/android/telephony/satellite/EarfcnRange.aidl b/telephony/java/android/telephony/satellite/EarfcnRange.aidl
new file mode 100644
index 000000000000..0b224d0b09bd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/EarfcnRange.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable EarfcnRange;
diff --git a/telephony/java/android/telephony/satellite/EarfcnRange.java b/telephony/java/android/telephony/satellite/EarfcnRange.java
new file mode 100644
index 000000000000..38043b570c2f
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/EarfcnRange.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * EARFCN (E-UTRA Absolute Radio Frequency Channel Number): A number that identifies a
+ * specific frequency channel in LTE/5G NR, used to define the carrier frequency.
+ * The range can be [0 ~ 65535] according to the 3GPP TS 36.101
+ *
+ * In satellite communication:
+ * - Efficient frequency allocation across a wide coverage area.
+ * - Handles Doppler shift due to satellite movement.
+ * - Manages interference with terrestrial networks.
+ *
+ * See 3GPP TS 36.101 and 38.101-1 for details.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public final class EarfcnRange implements Parcelable {
+
+ /**
+ * The start frequency of the earfcn range and is inclusive in the range
+ */
+ private int mStartEarfcn;
+
+ /**
+ * The end frequency of the earfcn range and is inclusive in the range.
+ */
+ private int mEndEarfcn;
+
+ private EarfcnRange(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStartEarfcn);
+ dest.writeInt(mEndEarfcn);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mStartEarfcn = in.readInt();
+ mEndEarfcn = in.readInt();
+ }
+
+ /**
+ * Constructor for the EarfcnRange class.
+ * The range can be [0 ~ 65535] according to the 3GPP TS 36.101
+ *
+ * @param startEarfcn The starting earfcn value.
+ * @param endEarfcn The ending earfcn value.
+ */
+ public EarfcnRange(@IntRange(from = 0, to = 65535) int endEarfcn,
+ @IntRange(from = 0, to = 65535) int startEarfcn) {
+ mEndEarfcn = endEarfcn;
+ mStartEarfcn = startEarfcn;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "startEarfcn: " + mStartEarfcn + ", " + "endEarfcn: " + mEndEarfcn;
+ }
+
+ @NonNull
+ public static final Creator<EarfcnRange> CREATOR = new Creator<EarfcnRange>() {
+ @Override
+ public EarfcnRange createFromParcel(Parcel in) {
+ return new EarfcnRange(in);
+ }
+
+ @Override
+ public EarfcnRange[] newArray(int size) {
+ return new EarfcnRange[size];
+ }
+ };
+
+ /**
+ * Returns the starting earfcn value for this range.
+ * It can be [0 ~ 65535] according to the 3GPP TS 36.101
+ *
+ * @return The starting earfcn.
+ */
+ public @IntRange(from = 0, to = 65535) int getStartEarfcn() {
+ return mStartEarfcn;
+ }
+
+ /**
+ * Returns the ending earfcn value for this range.
+ * It can be [0 ~ 65535] according to the 3GPP TS 36.101
+ *
+ * @return The ending earfcn.
+ */
+ public @IntRange(from = 0, to = 65535) int getEndEarfcn() {
+ return mEndEarfcn;
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
index a7eda482cb76..2730f90c4e5e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteCommunicationAllowedStateCallback.aidl
@@ -16,6 +16,8 @@
package android.telephony.satellite;
+import android.telephony.satellite.SatelliteAccessConfiguration;
+
/**
* Interface for satellite communication allowed state callback.
* @hide
@@ -29,4 +31,14 @@ oneway interface ISatelliteCommunicationAllowedStateCallback {
* @param allowed whether satellite communication state or not
*/
void onSatelliteCommunicationAllowedStateChanged(in boolean isAllowed);
+
+ /**
+ * Callback method invoked when the satellite access configuration changes
+ *
+ * @param The satellite access configuration associated with the current location.
+ * When satellite is not allowed at the current location,
+ * {@code satelliteRegionalConfiguration} will be null.
+ */
+ void onSatelliteAccessConfigurationChanged(in SatelliteAccessConfiguration
+ satelliteAccessConfiguration);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.aidl b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.aidl
new file mode 100644
index 000000000000..0214193a654f
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.telephony.satellite;
+
+ parcelable SatelliteAccessConfiguration; \ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
new file mode 100644
index 000000000000..c3ae70b48854
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteAccessConfiguration.java
@@ -0,0 +1,122 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.List;
+
+/**
+ * SatelliteAccessConfiguration is used to store satellite access configuration
+ * that will be applied to the satellite communication at the corresponding region.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public final class SatelliteAccessConfiguration implements Parcelable {
+ /**
+ * The list of satellites available at the current location.
+ */
+ @NonNull
+ private List<SatelliteInfo> mSatelliteInfoList;
+
+ /**
+ * The list of tag IDs associated with the current location
+ */
+ @NonNull
+ private int[] mTagIds;
+
+ /**
+ * Constructor for {@link SatelliteAccessConfiguration}.
+ *
+ * @param satelliteInfos The list of {@link SatelliteInfo} objects representing the satellites
+ * accessible with this configuration.
+ * @param tagIds The list of tag IDs associated with this configuration.
+ */
+ public SatelliteAccessConfiguration(@NonNull List<SatelliteInfo> satelliteInfos,
+ @NonNull int[] tagIds) {
+ mSatelliteInfoList = satelliteInfos;
+ mTagIds = tagIds;
+ }
+
+ public SatelliteAccessConfiguration(Parcel in) {
+ mSatelliteInfoList = in.createTypedArrayList(SatelliteInfo.CREATOR);
+ mTagIds = new int[in.readInt()];
+ in.readIntArray(mTagIds);
+ }
+
+ public static final Creator<SatelliteAccessConfiguration> CREATOR =
+ new Creator<SatelliteAccessConfiguration>() {
+ @Override
+ public SatelliteAccessConfiguration createFromParcel(Parcel in) {
+ return new SatelliteAccessConfiguration(in);
+ }
+
+ @Override
+ public SatelliteAccessConfiguration[] newArray(int size) {
+ return new SatelliteAccessConfiguration[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mSatelliteInfoList);
+ if (mTagIds != null && mTagIds.length > 0) {
+ dest.writeInt(mTagIds.length);
+ dest.writeIntArray(mTagIds);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /**
+ * Returns a list of {@link SatelliteInfo} objects representing the satellites
+ * associated with this object.
+ *
+ * @return The list of {@link SatelliteInfo} objects.
+ */
+ @NonNull
+ public List<SatelliteInfo> getSatelliteInfos() {
+ return mSatelliteInfoList;
+ }
+
+ /**
+ * Returns a list of tag IDs associated with this object.
+ *
+ * @return The list of tag IDs.
+ */
+ @NonNull
+ public int[] getTagIds() {
+ return mTagIds;
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
index 1a870202d096..bffb11f23d56 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCommunicationAllowedStateCallback.java
@@ -17,6 +17,7 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import com.android.internal.telephony.flags.Flags;
@@ -40,4 +41,17 @@ public interface SatelliteCommunicationAllowedStateCallback {
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed);
+
+ /**
+ * Callback method invoked when the satellite access configuration changes
+ *
+ * @param satelliteAccessConfiguration The satellite access configuration associated with
+ * the current location. When satellite is not allowed at
+ * the current location,
+ * {@code satelliteRegionalConfiguration} will be null.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ default void onSatelliteAccessConfigurationChanged(
+ @Nullable SatelliteAccessConfiguration satelliteAccessConfiguration) {};
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteInfo.aidl b/telephony/java/android/telephony/satellite/SatelliteInfo.aidl
new file mode 100644
index 000000000000..fc2303b080a5
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.telephony.satellite;
+
+ parcelable SatelliteInfo; \ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteInfo.java b/telephony/java/android/telephony/satellite/SatelliteInfo.java
new file mode 100644
index 000000000000..bca907e49993
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteInfo.java
@@ -0,0 +1,169 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * SatelliteInfo stores a satellite's identification, position, and frequency information
+ * facilitating efficient satellite communications.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public class SatelliteInfo implements Parcelable {
+ /**
+ * Unique identification number for the satellite.
+ * This ID is used to distinguish between different satellites in the network.
+ */
+ @NonNull
+ private UUID mId;
+
+ /**
+ * Position information of a satellite.
+ * This includes the longitude and altitude of the satellite.
+ */
+ private SatellitePosition mPosition;
+
+ /**
+ * The frequency bands to scan. Bands and earfcns won't overlap.
+ * Bands will be filled only if the whole band is needed.
+ * Maximum length of the vector is 8.
+ */
+ private int[] mBands;
+
+ /**
+ * EARFCN (E-UTRA Absolute Radio Frequency Channel Number) Ranges
+ * The supported frequency range list.
+ * Maximum length of the vector is 8.
+ */
+ private final List<EarfcnRange> mEarfcnRangeList;
+
+ protected SatelliteInfo(Parcel in) {
+ ParcelUuid parcelUuid = in.readParcelable(
+ ParcelUuid.class.getClassLoader(), ParcelUuid.class);
+ if (parcelUuid != null) {
+ mId = parcelUuid.getUuid();
+ }
+ mPosition = in.readParcelable(SatellitePosition.class.getClassLoader(),
+ SatellitePosition.class);
+ int numBands = in.readInt();
+ mBands = new int[numBands];
+ if (numBands > 0) {
+ for (int i = 0; i < numBands; i++) {
+ mBands[i] = in.readInt();
+ }
+ }
+ mEarfcnRangeList = in.createTypedArrayList(EarfcnRange.CREATOR);
+ }
+
+ /**
+ * Constructor for {@link SatelliteInfo}.
+ *
+ * @param satelliteId The ID of the satellite.
+ * @param satellitePosition The {@link SatellitePosition} of the satellite.
+ * @param bands The list of frequency bands supported by the satellite.
+ * @param earfcnRanges The list of {@link EarfcnRange} objects representing the EARFCN
+ * ranges supported by the satellite.
+ */
+ public SatelliteInfo(@NonNull UUID satelliteId, @NonNull SatellitePosition satellitePosition,
+ @NonNull int[] bands, @NonNull List<EarfcnRange> earfcnRanges) {
+ mId = satelliteId;
+ mPosition = satellitePosition;
+ mBands = bands;
+ mEarfcnRangeList = earfcnRanges;
+ }
+
+ public static final Creator<SatelliteInfo> CREATOR = new Creator<SatelliteInfo>() {
+ @Override
+ public SatelliteInfo createFromParcel(Parcel in) {
+ return new SatelliteInfo(in);
+ }
+
+ @Override
+ public SatelliteInfo[] newArray(int size) {
+ return new SatelliteInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(new ParcelUuid(mId), flags);
+ dest.writeParcelable(mPosition, flags);
+ if (mBands != null && mBands.length > 0) {
+ dest.writeInt(mBands.length);
+ dest.writeIntArray(mBands);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeTypedList(mEarfcnRangeList);
+ }
+
+ /**
+ * Returns the ID of the satellite.
+ *
+ * @return The satellite ID.
+ */
+ @NonNull
+ public UUID getSatelliteId() {
+ return mId;
+ }
+
+ /**
+ * Returns the position of the satellite.
+ *
+ * @return The {@link SatellitePosition} of the satellite.
+ */
+ public SatellitePosition getSatellitePosition() {
+ return mPosition;
+ }
+
+ /**
+ * Returns the list of frequency bands supported by the satellite.
+ *
+ * @return The list of frequency bands.
+ */
+ @NonNull
+ public int[] getBands() {
+ return mBands;
+ }
+
+ /**
+ * Returns the list of EARFCN ranges supported by the satellite.
+ *
+ * @return The list of {@link EarfcnRange} objects.
+ */
+ @NonNull
+ public List<EarfcnRange> getEarfcnRanges() {
+ return mEarfcnRangeList;
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7be3f337e43a..7e3d99a5c4ac 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -36,6 +36,7 @@ import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
@@ -272,6 +273,14 @@ public final class SatelliteManager {
public static final String KEY_DEPROVISION_SATELLITE_TOKENS = "deprovision_satellite";
/**
+ * Bundle key to get the response from
+ * {@link #requestSatelliteAccessConfigurationForCurrentLocation(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+ public static final String KEY_SATELLITE_ACCESS_CONFIGURATION =
+ "satellite_access_configuration";
+
+ /**
* The request was successfully processed.
* @hide
*/
@@ -483,43 +492,43 @@ public final class SatelliteManager {
/**
* Telephony framework needs to access the current location of the device to perform the
* request. However, location in the settings is disabled by users.
- *
* @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25;
/**
* Telephony framework needs to access the current location of the device to perform the
* request. However, Telephony fails to fetch the current location from location service.
- *
* @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26;
/**
* Emergency call is in progress.
- *
* @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
/**
* Disabling satellite is in progress.
- *
* @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28;
/**
* Enabling satellite is in progress.
- *
* @hide
*/
- @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29;
/** @hide */
@@ -715,7 +724,7 @@ public final class SatelliteManager {
public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2;
/**
- * This intent will be broadcasted if there are any change to list of subscriber informations.
+ * This intent will be broadcasted if there are any change to list of subscriber information.
* This intent will be sent only to the app with component defined in
* config_satellite_carrier_roaming_esos_provisioned_class and package defined in
* config_satellite_gateway_service_package
@@ -1349,12 +1358,16 @@ public final class SatelliteManager {
* The satellite modem is being powered on.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8;
/**
* The satellite modem is being powered off.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9;
/**
@@ -1414,6 +1427,8 @@ public final class SatelliteManager {
* there is any incoming message.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
/**
@@ -1421,6 +1436,8 @@ public final class SatelliteManager {
* is the last message to emergency service provider indicating still needs help.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4;
/**
@@ -1428,12 +1445,16 @@ public final class SatelliteManager {
* is the last message to emergency service provider indicating no more help is needed.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
/**
* Datagram type indicating that the message to be sent or received is of type SMS.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int DATAGRAM_TYPE_SMS = 6;
/**
@@ -1441,6 +1462,8 @@ public final class SatelliteManager {
* for pending incoming SMS.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7;
/** @hide */
@@ -1461,6 +1484,8 @@ public final class SatelliteManager {
* Satellite communication restricted by user.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0;
/**
@@ -2316,6 +2341,68 @@ public final class SatelliteManager {
}
/**
+ * Request to get satellite access configuration for the current location.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return a {@code SatelliteAccessConfiguration} with value the regional
+ * satellite access configuration at the current location.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteResult}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public void requestSatelliteAccessConfigurationForCurrentLocation(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<SatelliteAccessConfiguration, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_ACCESS_CONFIGURATION)) {
+ SatelliteAccessConfiguration satelliteAccessConfiguration =
+ resultData.getParcelable(KEY_SATELLITE_ACCESS_CONFIGURATION,
+ SatelliteAccessConfiguration.class);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(satelliteAccessConfiguration)));
+ } else {
+ loge("KEY_SATELLITE_ACCESS_CONFIGURATION does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestSatelliteAccessConfigurationForCurrentLocation(receiver);
+ } else {
+ loge("requestSatelliteAccessConfigurationForCurrentLocation() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestSatelliteAccessConfigurationForCurrentLocation() RemoteException: "
+ + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
+ /**
* Request to get the duration in seconds after which the satellite will be visible.
* This will be {@link Duration#ZERO} if the satellite is currently visible.
*
@@ -2420,7 +2507,7 @@ public final class SatelliteManager {
* <li>There is no satellite communication restriction, which is added by
* {@link #addAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li>
* <li>The carrier config {@link
- * android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
+ * CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
* {@code true}.</li>
* </ul>
*
@@ -2743,7 +2830,7 @@ public final class SatelliteManager {
* <p>
* Note: This API is specifically designed for OEM enabled satellite connectivity only.
* For satellite connectivity enabled using carrier roaming, please refer to
- * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyCallback.SignalStrengthsListener}, and
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
* </p>
*
@@ -2814,7 +2901,7 @@ public final class SatelliteManager {
* <p>
* Note: This API is specifically designed for OEM enabled satellite connectivity only.
* For satellite connectivity enabled using carrier roaming, please refer to
- * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+ * {@link TelephonyCallback.SignalStrengthsListener}, and
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
* </p>
*
@@ -3131,6 +3218,15 @@ public final class SatelliteManager {
() -> callback.onSatelliteCommunicationAllowedStateChanged(
isAllowed)));
}
+
+ @Override
+ public void onSatelliteAccessConfigurationChanged(
+ @Nullable SatelliteAccessConfiguration
+ satelliteAccessConfiguration) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteAccessConfigurationChanged(
+ satelliteAccessConfiguration)));
+ }
};
sSatelliteCommunicationAllowedStateCallbackMap.put(callback, internalCallback);
return telephony.registerForCommunicationAllowedStateChanged(
diff --git a/telephony/java/android/telephony/satellite/SatellitePosition.aidl b/telephony/java/android/telephony/satellite/SatellitePosition.aidl
new file mode 100644
index 000000000000..a8028eb48ee7
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatellitePosition.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.telephony.satellite;
+
+ parcelable SatellitePosition; \ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatellitePosition.java b/telephony/java/android/telephony/satellite/SatellitePosition.java
new file mode 100644
index 000000000000..1e8c0180f456
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatellitePosition.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * The position of a satellite in Earth orbit.
+ *
+ * Longitude is the angular distance, measured in degrees, east or west of the prime longitude line
+ * ranging from -180 to 180 degrees
+ * Altitude is the distance from the center of the Earth to the satellite, measured in kilometers
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public class SatellitePosition implements Parcelable {
+
+ /**
+ * The longitude of the satellite in degrees, ranging from -180 to 180 degrees
+ */
+ private double mLongitudeDegree;
+
+ /**
+ * The distance from the center of the earth to the satellite, measured in kilometers
+ */
+ private double mAltitudeKm;
+
+ /**
+ * Constructor for {@link SatellitePosition} used to create an instance from a {@link Parcel}.
+ *
+ * @param in The {@link Parcel} to read the satellite position data from.
+ */
+ public SatellitePosition(Parcel in) {
+ mLongitudeDegree = in.readDouble();
+ mAltitudeKm = in.readDouble();
+ }
+
+ /**
+ * Constructor for {@link SatellitePosition}.
+ *
+ * @param longitudeDegree The longitude of the satellite in degrees.
+ * @param altitudeKm The altitude of the satellite in kilometers.
+ */
+ public SatellitePosition(double longitudeDegree, double altitudeKm) {
+ mLongitudeDegree = longitudeDegree;
+ mAltitudeKm = altitudeKm;
+ }
+
+ public static final Creator<SatellitePosition> CREATOR = new Creator<SatellitePosition>() {
+ @Override
+ public SatellitePosition createFromParcel(Parcel in) {
+ return new SatellitePosition(in);
+ }
+
+ @Override
+ public SatellitePosition[] newArray(int size) {
+ return new SatellitePosition[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeDouble(mLongitudeDegree);
+ dest.writeDouble(mAltitudeKm);
+ }
+
+ /**
+ * Returns the longitude of the satellite in degrees, ranging from -180 to 180 degrees.
+ *
+ * @return The longitude of the satellite.
+ */
+ public double getLongitudeDegrees() {
+ return mLongitudeDegree;
+ }
+
+ /**
+ * Returns the altitude of the satellite in kilometers
+ *
+ * @return The altitude of the satellite.
+ */
+ public double getAltitudeKm() {
+ return mAltitudeKm;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 62cbb02c9fc7..210200be4cf3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -886,7 +886,7 @@ interface ITelephony {
/**
* @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
*/
- boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ boolean setBoundImsServiceOverride(int slotIndex, int userId, boolean isCarrierService,
in int[] featureTypes, in String packageName);
/**
@@ -2999,6 +2999,16 @@ interface ITelephony {
void requestIsCommunicationAllowedForCurrentLocation(int subId, in ResultReceiver receiver);
/**
+ * Request to get satellite access configuration for the current location.
+ *
+ * @param receiver Result receiver to get the error code of the request
+ * and satellite access configuration for the current location.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void requestSatelliteAccessConfigurationForCurrentLocation(in ResultReceiver receiver);
+
+ /**
* Request to get the time after which the satellite will be visible.
*
* @param receiver Result receiver to get the error code of the request and the requested
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 332b9b832037..9a9a331a3753 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -21,7 +21,7 @@ import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Region
import android.os.SystemClock
-import android.platform.uiautomator_helpers.DeviceHelpers
+import android.platform.uiautomatorhelpers.DeviceHelpers
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -159,7 +159,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
) {
val caption = getCaptionForTheApp(wmHelper, device)
val maximizeButton = getMaximizeButtonForTheApp(caption)
- maximizeButton?.longClick()
+ maximizeButton.longClick()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 1574d1b7ce6f..61400edba165 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -757,7 +757,31 @@ class KeyGestureControllerTests {
intArrayOf(KeyEvent.KEYCODE_MINUS),
KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
+ ),
+ TestData(
+ "META + ALT + '-' -> Magnifier Zoom Out",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + '=' -> Magnifier Zoom In",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@@ -770,6 +794,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -787,6 +812,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -995,11 +1021,28 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
),
+ TestData(
+ "LOCK -> Lock Screen",
+ intArrayOf(KeyEvent.KEYCODE_LOCK),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ intArrayOf(KeyEvent.KEYCODE_LOCK),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "FULLSCREEN -> Maximizes a task to fit the screen",
+ intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@Test
@Parameters(method = "systemKeysTestArguments")
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
fun testSystemKeys(test: TestData) {
setupKeyGestureController()
testKeyGestureInternal(test)
@@ -1029,6 +1072,9 @@ class KeyGestureControllerTests {
KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
+ KeyEvent.KEYCODE_DO_NOT_DISTURB,
+ KeyEvent.KEYCODE_LOCK,
+ KeyEvent.KEYCODE_FULLSCREEN
)
val handler = KeyGestureHandler { _, _ -> false }
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index 07b733830bd3..0da4521fca71 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -143,6 +143,38 @@ public class VibratorManagerServicePermissionTest {
}
@Test
+ public void testStartVendorVibrationSessionWithoutVibratePermissionFails() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+ Manifest.permission.START_VIBRATION_SESSIONS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
+ public void testStartVendorVibrationSessionWithoutVibrateVendorEffectsPermissionFails()
+ throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE,
+ Manifest.permission.START_VIBRATION_SESSIONS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
+ public void testStartVendorVibrationSessionWithoutStartSessionPermissionFails()
+ throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.VIBRATE,
+ Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+ expectSecurityException("VIBRATE");
+ mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+ new int[] { 1 }, ATTRS, "testVibrate", null);
+ }
+
+ @Test
public void testCancelVibrateFails() throws RemoteException {
expectSecurityException("VIBRATE");
mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder());
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 1abe77fd3ceb..f260e2733843 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -188,7 +188,7 @@ object SystemFeaturesGenerator {
?: throw IllegalArgumentException(
"Invalid feature version input for $name: ${featureArgs[1]}"
)
- FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+ FeatureInfo(name, featureVersion, readonly = true)
}
}
}