summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt33
-rw-r--r--core/api/system-current.txt8
-rw-r--r--core/api/test-current.txt12
-rw-r--r--core/java/android/app/DisabledWallpaperManager.java12
-rw-r--r--core/java/android/app/IWallpaperManager.aidl10
-rw-r--r--core/java/android/app/WallpaperManager.java99
-rw-r--r--core/java/android/app/appfunctions/AppFunctionException.java1
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/supervision/flags.aconfig10
-rw-r--r--core/java/android/app/wallpaper.aconfig18
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java66
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java18
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java2
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java68
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java95
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java95
-rw-r--r--core/java/android/hardware/devicestate/DeviceState.java20
-rw-r--r--core/java/android/hardware/devicestate/feature/flags.aconfig9
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java8
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig4
-rw-r--r--core/java/android/hardware/usb/UsbManager.java269
-rw-r--r--core/java/android/hardware/usb/flags/usb_framework_flags.aconfig8
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java34
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java28
-rw-r--r--core/java/android/os/WorkSource.java8
-rw-r--r--core/java/android/os/flags.aconfig9
-rw-r--r--core/java/android/permission/flags.aconfig14
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/security/flags.aconfig2
-rw-r--r--core/java/android/service/settings/preferences/ISettingsPreferenceService.aidl18
-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/text/flags/flags.aconfig4
-rw-r--r--core/java/android/view/ViewRootImpl.java16
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java84
-rw-r--r--core/java/android/window/DesktopModeFlags.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig10
-rw-r--r--core/java/com/android/internal/accessibility/common/ShortcutConstants.java10
-rw-r--r--core/java/com/android/internal/accessibility/util/ShortcutUtils.java3
-rw-r--r--core/jni/android_hardware_Camera.cpp2
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/AndroidTest.xml2
-rw-r--r--core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java52
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java9
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--graphics/java/android/graphics/Paint.java2
-rw-r--r--graphics/java/android/graphics/text/PositionedGlyphs.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java35
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java61
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java15
-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/DefaultTransitionHandler.java2
-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/CaptionWindowDecorViewModel.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt77
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt41
-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/WindowDecoration.java112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt56
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt52
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt51
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt173
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt115
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java178
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt68
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java207
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java28
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java1
-rw-r--r--libs/hwui/FeatureFlags.h4
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig10
-rw-r--r--libs/hwui/hwui/MinikinUtils.h2
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp6
-rw-r--r--media/java/android/media/MediaCas.java9
-rw-r--r--media/java/android/media/MediaCodec.java54
-rw-r--r--media/java/android/media/MediaRouter2.java22
-rw-r--r--media/java/android/media/RoutingSessionInfo.java52
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--nfc/api/system-current.txt22
-rw-r--r--nfc/java/android/nfc/INfcOemExtensionCallback.aidl2
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java12
-rw-r--r--nfc/java/android/nfc/OemLogItems.aidl19
-rw-r--r--nfc/java/android/nfc/OemLogItems.java325
-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/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt6
-rw-r--r--packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt25
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt6
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt9
-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/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt10
-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.xml4
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig22
-rw-r--r--packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml19
-rw-r--r--packages/SystemUI/customization/res/values-sw600dp/dimens.xml20
-rw-r--r--packages/SystemUI/customization/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt20
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt120
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt147
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt24
-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/qs/panels/ui/compose/EditTileListStateTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java25
-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/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt86
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml1
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/config.xml3
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt185
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt15
-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/keyguard/ui/viewmodel/KeyguardClockViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt9
-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/wallet/ui/WalletScreenController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java2
-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/qs/panels/ui/compose/DragAndDropTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java78
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt4
-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/LargeTileSpanRepositoryKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt1
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java133
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java2
-rw-r--r--services/core/java/com/android/server/BootReceiver.java14
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java147
-rw-r--r--services/core/java/com/android/server/am/BroadcastSkipPolicy.java100
-rw-r--r--services/core/java/com/android/server/am/broadcasts_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java74
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java6
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java67
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java48
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java80
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java19
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java25
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java62
-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/WindowContainer.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java51
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java41
-rw-r--r--services/java/com/android/server/SystemServer.java3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java195
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt6
-rw-r--r--services/tests/mockingservicestests/Android.bp5
-rw-r--r--services/tests/mockingservicestests/AndroidTest.xml6
-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.java173
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java292
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java192
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java168
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java26
-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.java86
-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.aidl10
-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.kt24
-rw-r--r--tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java108
-rw-r--r--tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java32
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java18
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java12
273 files changed, 8708 insertions, 1283 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index c0b6ab6a6755..9fc350d75bbc 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19373,6 +19373,7 @@ package android.hardware.camera2 {
field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_MODES;
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_PRIORITY_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> CONTROL_AE_COMPENSATION_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Rational> CONTROL_AE_COMPENSATION_STEP;
@@ -19690,6 +19691,9 @@ package android.hardware.camera2 {
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0
field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_OFF = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY = 1; // 0x1
field public static final int CONTROL_AE_STATE_CONVERGED = 2; // 0x2
field public static final int CONTROL_AE_STATE_FLASH_REQUIRED = 4; // 0x4
field public static final int CONTROL_AE_STATE_INACTIVE = 0; // 0x0
@@ -19783,6 +19787,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
@@ -19972,6 +19978,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_AE_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AE_PRECAPTURE_TRIGGER;
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AE_PRIORITY_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Range<java.lang.Integer>> CONTROL_AE_TARGET_FPS_RANGE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_MODE;
@@ -19990,6 +19997,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;
@@ -20064,6 +20072,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_AE_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AE_PRECAPTURE_TRIGGER;
+ field @FlaggedApi("com.android.internal.camera.flags.ae_priority") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AE_PRIORITY_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AE_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Range<java.lang.Integer>> CONTROL_AE_TARGET_FPS_RANGE;
@@ -20088,6 +20097,7 @@ 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;
@@ -20910,6 +20920,8 @@ package android.hardware.usb {
method public boolean hasPermission(android.hardware.usb.UsbDevice);
method public boolean hasPermission(android.hardware.usb.UsbAccessory);
method public android.os.ParcelFileDescriptor openAccessory(android.hardware.usb.UsbAccessory);
+ method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.InputStream openAccessoryInputStream(@NonNull android.hardware.usb.UsbAccessory);
+ method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.OutputStream openAccessoryOutputStream(@NonNull android.hardware.usb.UsbAccessory);
method public android.hardware.usb.UsbDeviceConnection openDevice(android.hardware.usb.UsbDevice);
method public void requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent);
method public void requestPermission(android.hardware.usb.UsbAccessory, android.app.PendingIntent);
@@ -21012,6 +21024,7 @@ package android.inputmethodservice {
method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
method public boolean onEvaluateFullscreenMode();
method @CallSuper public boolean onEvaluateInputViewShown();
@@ -22899,6 +22912,7 @@ package android.media {
method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException);
method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
+ method @FlaggedApi("android.media.codec.subsession_metrics") public void onMetricsFlushed(@NonNull android.media.MediaCodec, @NonNull android.os.PersistableBundle);
method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void onOutputBuffersAvailable(@NonNull android.media.MediaCodec, int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
method public abstract void onOutputFormatChanged(@NonNull android.media.MediaCodec, @NonNull android.media.MediaFormat);
@@ -42159,6 +42173,25 @@ package android.service.settings.preferences {
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();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 71c431216bf8..0e521c664340 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1272,13 +1272,21 @@ package android.app {
public class WallpaperManager {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
+ method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
+ method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point);
method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
+ method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean);
method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
method public void setDisplayOffset(android.os.IBinder, int, int);
+ method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray<android.graphics.Rect>, boolean, int) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
+ field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_LANDSCAPE = 1; // 0x1
+ field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_PORTRAIT = 0; // 0x0
+ field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_LANDSCAPE = 3; // 0x3
+ field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_PORTRAIT = 2; // 0x2
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8dd12172fdc5..c8ecfa94ec87 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -535,9 +535,13 @@ package android.app {
public class WallpaperManager {
method @Nullable public android.graphics.Bitmap getBitmap();
method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
+ method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean);
+ method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>);
method public boolean isLockscreenLiveWallpaperEnabled();
method @Nullable public android.graphics.Rect peekBitmapDimensions();
method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
+ method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable android.graphics.Bitmap, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
+ method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
method public boolean shouldEnableWideColorGamut();
method public boolean wallpaperSupportsWcg(int);
@@ -3247,6 +3251,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 {
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index b06fb9e2f284..233dc75b810f 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -177,6 +177,13 @@ final class DisabledWallpaperManager extends WallpaperManager {
}
@Override
+ @NonNull
+ public SparseArray<Rect> getBitmapCrops(int which) {
+ unsupported();
+ return new SparseArray<>();
+ }
+
+ @Override
public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
@Nullable Map<Point, Rect> cropHints) {
return unsupported();
@@ -358,8 +365,9 @@ final class DisabledWallpaperManager extends WallpaperManager {
@Override
- public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
- boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ public int setStreamWithCrops(@NonNull InputStream bitmapData,
+ @NonNull SparseArray<Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which
+ ) throws IOException {
return unsupportedInt();
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index f693e9ba11ec..6449ea1742a1 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -97,6 +97,16 @@ interface IWallpaperManager {
List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);
/**
+ * For a given user, if the wallpaper of the specified which is an ImageWallpaper, return
+ * a bundle which is a Map<Integer, Rect> containing the custom cropHints that were sent to
+ * setBitmapWithCrops or setStreamWithCrops. These crops are relative to the original bitmap.
+ * If the wallpaper isn't an ImageWallpaper, return null.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+ @SuppressWarnings(value={"untyped-collection"})
+ Bundle getCurrentBitmapCrops(int which, int userId);
+
+ /**
* Return how a bitmap of a given size would be cropped for a given list of display sizes when
* set with the given suggested crops.
* @hide
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 479f3df9affb..abb2dd465576 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -19,9 +19,10 @@ package android.app;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
+import static android.app.Flags.FLAG_CUSTOMIZATION_PACKS_APIS;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
-import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.android.window.flags.Flags.multiCrop;
@@ -342,24 +343,32 @@ public class WallpaperManager {
* Portrait orientation of most screens
* @hide
*/
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
public static final int ORIENTATION_PORTRAIT = 0;
/**
* Landscape orientation of most screens
* @hide
*/
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
public static final int ORIENTATION_LANDSCAPE = 1;
/**
* Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
* @hide
*/
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
public static final int ORIENTATION_SQUARE_PORTRAIT = 2;
/**
* Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
* @hide
*/
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
public static final int ORIENTATION_SQUARE_LANDSCAPE = 3;
/**
@@ -368,7 +377,9 @@ public class WallpaperManager {
* @return the corresponding {@link ScreenOrientation}.
* @hide
*/
- public static @ScreenOrientation int getOrientation(Point screenSize) {
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
+ public static @ScreenOrientation int getOrientation(@NonNull Point screenSize) {
float ratio = ((float) screenSize.x) / screenSize.y;
// ratios between 3/4 and 4/3 are considered square
return ratio >= 4 / 3f ? ORIENTATION_LANDSCAPE
@@ -1623,14 +1634,15 @@ public class WallpaperManager {
* If false, return areas relative to the cropped bitmap.
* @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
* to what is displayed. The Rect may have a larger width/height ratio than the screen
- * due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
- * Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
+ * due to parallax. Return an empty list if the wallpaper is not an ImageWallpaper.
+ * Also return an empty list when called with which={@link #FLAG_LOCK} if there is a
* shared home + lock wallpaper.
* @hide
*/
@FlaggedApi(FLAG_MULTI_CROP)
+ @TestApi
@RequiresPermission(READ_WALLPAPER_INTERNAL)
- @Nullable
+ @NonNull
public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
@SetWallpaperFlags int which, boolean originalBitmap) {
checkExactlyOneWallpaperFlagSet(which);
@@ -1653,6 +1665,52 @@ public class WallpaperManager {
}
/**
+ * For the current user, if the wallpaper of the specified destination is an ImageWallpaper,
+ * return the custom crops of the wallpaper, that have been provided for example via
+ * {@link #setStreamWithCrops}. These crops are relative to the original bitmap.
+ * <p>
+ * This method helps apps that change wallpapers provide an undo option. Calling
+ * {@link #setStreamWithCrops(InputStream, SparseArray, boolean, int)} with this SparseArray and
+ * the current original bitmap file, that can be obtained with {@link #getWallpaperFile(int,
+ * boolean)} with {@code getCropped=false}, will exactly lead to the current wallpaper state.
+ *
+ * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @return A map from {{@link #ORIENTATION_PORTRAIT}, {@link #ORIENTATION_LANDSCAPE},
+ * {@link #ORIENTATION_SQUARE_PORTRAIT}, {{@link #ORIENTATION_SQUARE_LANDSCAPE}}} to
+ * Rect, representing the custom cropHints. The map can be empty and will only contains
+ * entries for screen orientations for which a custom crop was provided. If no custom
+ * crop is provided for an orientation, the system will infer the crop based on the
+ * custom crops of the other orientations; or center-align the full image if no custom
+ * crops are provided at all.
+ * <p>
+ * Return an empty map if the wallpaper is not an ImageWallpaper. Also return
+ * an empty map when called with which={@link #FLAG_LOCK} if there is a shared
+ * home + lock wallpaper.
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
+ @RequiresPermission(READ_WALLPAPER_INTERNAL)
+ @NonNull
+ public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) {
+ checkExactlyOneWallpaperFlagSet(which);
+ try {
+ Bundle bundle = sGlobals.mService.getCurrentBitmapCrops(which, mContext.getUserId());
+ SparseArray<Rect> result = new SparseArray<>();
+ if (bundle == null) return result;
+ for (String key : bundle.keySet()) {
+ int intKey = Integer.parseInt(key);
+ Rect rect = bundle.getParcelable(key, Rect.class);
+ result.put(intKey, rect);
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* For preview purposes.
* Return how a bitmap of a given size would be cropped for a given list of display sizes, if
* it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
@@ -1664,7 +1722,8 @@ public class WallpaperManager {
* @hide
*/
@FlaggedApi(FLAG_MULTI_CROP)
- @Nullable
+ @TestApi
+ @NonNull
public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
@Nullable Map<Point, Rect> cropHints) {
try {
@@ -1890,9 +1949,14 @@ public class WallpaperManager {
* defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
* @param getCropped If true the cropped file will be retrieved, if false the original will
* be retrieved.
- *
+ * @return A ParcelFileDescriptor for the wallpaper bitmap of the given destination, if it's an
+ * ImageWallpaper wallpaper. Return {@code null} if the wallpaper is not an
+ * ImageWallpaper. Also return {@code null} when called with
+ * which={@link #FLAG_LOCK} if there is a shared home + lock wallpaper.
* @hide
*/
+ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+ @SystemApi
@Nullable
public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) {
return getWallpaperFile(which, mContext.getUserId(), getCropped);
@@ -2371,7 +2435,6 @@ public class WallpaperManager {
/**
* Version of setBitmap that defines how the wallpaper will be positioned for different
* display sizes.
- * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
* @param cropHints map from screen dimensions to a sub-region of the image to display for those
* dimensions. The {@code Rect} sub-region may have a larger width/height ratio
* than the screen dimensions to apply a horizontal parallax effect. If the
@@ -2380,6 +2443,7 @@ public class WallpaperManager {
* @hide
*/
@FlaggedApi(FLAG_MULTI_CROP)
+ @TestApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
@@ -2562,7 +2626,6 @@ public class WallpaperManager {
/**
* Version of setStream that defines how the wallpaper will be positioned for different
* display sizes.
- * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
* @param cropHints map from screen dimensions to a sub-region of the image to display for those
* dimensions. The {@code Rect} sub-region may have a larger width/height ratio
* than the screen dimensions to apply a horizontal parallax effect. If the
@@ -2571,9 +2634,11 @@ public class WallpaperManager {
* @hide
*/
@FlaggedApi(FLAG_MULTI_CROP)
+ @TestApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
- public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
- boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ public int setStreamWithCrops(@NonNull InputStream bitmapData,
+ @NonNull Map<Point, Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which)
+ throws IOException {
SparseArray<Rect> crops = new SparseArray<>();
cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
return setStreamWithCrops(bitmapData, crops, allowBackup, which);
@@ -2583,15 +2648,21 @@ public class WallpaperManager {
* Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
* {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
* WallpaperBackupAgent stores orientations rather than the exact display size.
- * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @param bitmapData A stream containing the raw data to install as a wallpaper. This
+ * data can be in any format handled by {@link BitmapRegionDecoder}.
* @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
* for that screen orientation.
+ * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+ * image for restore to a future device; {@code false} otherwise.
+ * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
* @hide
*/
@FlaggedApi(FLAG_MULTI_CROP)
+ @SystemApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
- public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
- boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ public int setStreamWithCrops(@NonNull InputStream bitmapData,
+ @NonNull SparseArray<Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which)
+ throws IOException {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
throw new RuntimeException(new DeadSystemException());
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
index d33b5055f9cc..cbd1d932ab00 100644
--- a/core/java/android/app/appfunctions/AppFunctionException.java
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -141,6 +141,7 @@ public final class AppFunctionException extends Exception implements Parcelable
*/
public AppFunctionException(
@ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ super(errorMessage);
mErrorCode = errorCode;
mErrorMessage = errorMessage;
mExtras = Objects.requireNonNull(extras);
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index ee93870be055..6934e9883840 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -100,16 +100,6 @@ flag {
}
flag {
- name: "visit_person_uri"
- namespace: "systemui"
- description: "Guards the security fix that ensures all URIs Person.java are valid"
- bug: "281044385"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "notification_expansion_optional"
namespace: "systemui"
description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions."
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index bcb5b3636c95..d5e696d49ff4 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -7,4 +7,12 @@ flag {
namespace: "supervision"
description: "Flag to enable the SupervisionService"
bug: "340351729"
-} \ No newline at end of file
+}
+
+flag {
+ name: "supervision_api_on_wear"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the SupervisionService on Wear devices"
+ bug: "373358935"
+}
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index 4b880d030413..f750a844f4ff 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -22,3 +22,21 @@ flag {
bug: "347235611"
is_exported: true
}
+
+flag {
+ name: "customization_packs_apis"
+ is_exported: true
+ namespace: "systemui"
+ description: "Move APIs related to bitmap and crops to @SystemApi."
+ bug: "372344184"
+}
+
+flag {
+ name: "accurate_wallpaper_downsampling"
+ namespace: "systemui"
+ description: "Accurate downsampling of wallpaper bitmap for high resolution images"
+ bug: "355665230"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 34c3f5798bc5..e9e8578af787 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -38,6 +38,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.util.EmptyArray;
import android.util.Pair;
import android.util.Slog;
@@ -565,10 +566,7 @@ public class ApkLiteParseUtils {
usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
/*allowDuplicates=*/ true);
- // We allow ":" delimiters in the SHA declaration as this is the format
- // emitted by the certtool making it easy for developers to copy/paste.
- // TODO(372862145): Add test for this replacement
- usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+ usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest);
if ("".equals(usesSdkCertDigest)) {
// Test-only uses-sdk-library empty certificate digest override.
@@ -618,18 +616,23 @@ public class ApkLiteParseUtils {
usesStaticLibrariesVersions, usesStaticLibVersion,
/*allowDuplicates=*/ true);
- // We allow ":" delimiters in the SHA declaration as this is the format
- // emitted by the certtool making it easy for developers to copy/paste.
- // TODO(372862145): Add test for this replacement
- usesStaticLibCertDigest =
- usesStaticLibCertDigest.replace(":", "").toLowerCase();
+ usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest);
+
+ ParseResult<String[]> certResult =
+ parseAdditionalCertificates(input, parser);
+ if (certResult.isError()) {
+ return input.error(certResult);
+ }
+ String[] additionalCertSha256Digests = certResult.getResult();
+ String[] certSha256Digests =
+ new String[additionalCertSha256Digests.length + 1];
+ certSha256Digests[0] = usesStaticLibCertDigest;
+ System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+ 1, additionalCertSha256Digests.length);
- // TODO(372862145): Add support for multiple signer for app targeting
- // O-MR1
usesStaticLibrariesCertDigests = ArrayUtils.appendElement(
String[].class, usesStaticLibrariesCertDigests,
- new String[]{usesStaticLibCertDigest},
- /*allowDuplicates=*/ true);
+ certSha256Digests, /*allowDuplicates=*/ true);
break;
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
@@ -809,6 +812,43 @@ public class ApkLiteParseUtils {
declaredLibraries));
}
+ private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
+ XmlResourceParser parser) throws XmlPullParserException, IOException {
+ String[] certSha256Digests = EmptyArray.STRING;
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String nodeName = parser.getName();
+ if (nodeName.equals("additional-certificate")) {
+ String certSha256Digest = parser.getAttributeValue(
+ ANDROID_RES_NAMESPACE, "certDigest");
+ if (TextUtils.isEmpty(certSha256Digest)) {
+ return input.error("Bad additional-certificate declaration with empty"
+ + " certDigest:" + certSha256Digest);
+ }
+
+ certSha256Digest = normalizeCertDigest(certSha256Digest);
+ certSha256Digests = ArrayUtils.appendElement(String.class,
+ certSha256Digests, certSha256Digest);
+ }
+ }
+
+ return input.success(certSha256Digests);
+ }
+
+ /**
+ * We allow ":" delimiters in the SHA declaration as this is the format emitted by the
+ * certtool making it easy for developers to copy/paste.
+ */
+ private static String normalizeCertDigest(String certDigest) {
+ return certDigest.replace(":", "").toLowerCase();
+ }
+
private static boolean isDeviceAdminReceiver(
XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
throws XmlPullParserException, IOException {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a37648f7e45d..4ca20059f4eb 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1436,6 +1436,24 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<android.util.Range<Float>>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference<android.util.Range<Float>>() {{ }});
/**
+ * <p>List of auto-exposure priority modes for {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode}
+ * that are supported by this camera device.</p>
+ * <p>This entry lists the valid modes for
+ * {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for this camera device.
+ * If no AE priority modes are available for a device, this will only list OFF.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final Key<int[]> CONTROL_AE_AVAILABLE_PRIORITY_MODES =
+ new Key<int[]>("android.control.aeAvailablePriorityModes", int[].class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b7856303fc05..75e20582b7b4 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -994,7 +994,7 @@ public final class CameraManager {
AttributionSourceState contextAttributionSourceState =
contextAttributionSource.asState();
- if (Flags.useContextAttributionSource() && useContextAttributionSource) {
+ if (Flags.dataDeliveryPermissionChecks() && useContextAttributionSource) {
return contextAttributionSourceState;
} else {
AttributionSourceState clientAttribution =
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 987e2ad768b0..8d36fbdc8b10 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3371,6 +3371,74 @@ 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#CONTROL_AE_PRIORITY_MODE
+ //
+
+ /**
+ * <p>Disable AE priority mode. This is the default value.</p>
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
+ */
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final int CONTROL_AE_PRIORITY_MODE_OFF = 0;
+
+ /**
+ * <p>The camera device's auto-exposure routine is active and
+ * prioritizes the application-selected ISO ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}).</p>
+ * <p>The application has control over {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} while
+ * the application's values for {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored.</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
+ */
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY = 1;
+
+ /**
+ * <p>The camera device's auto-exposure routine is active and
+ * prioritizes the application-selected exposure time
+ * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}).</p>
+ * <p>The application has control over {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime} while
+ * the application's values for {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored.</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
+ */
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final int CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY = 2;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 8142bbe9b838..496d316eb028 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;
@@ -1407,7 +1406,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* application's selected exposure time, sensor sensitivity,
* and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
* {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
- * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is
+ * enabled, the relevant priority CaptureRequest settings will not be overridden.
+ * See {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for more details. If one of the FLASH modes
* is selected, the camera device's flash unit controls are
* also overridden.</p>
* <p>The FLASH modes are only available if the camera device
@@ -1441,6 +1442,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureRequest#FLASH_MODE
@@ -2668,6 +2670,85 @@ 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>Turn on AE priority mode.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is
+ * AUTO and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to one of its
+ * ON modes, with the exception of ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+ * <p>When a priority mode is enabled, the camera device's
+ * auto-exposure routine will maintain the application's
+ * selected parameters relevant to the priority mode while overriding
+ * the remaining exposure parameters
+ * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). For example, if
+ * SENSOR_SENSITIVITY_PRIORITY mode is enabled, the camera device will
+ * maintain the application-selected {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}
+ * while adjusting {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}
+ * and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}. The overridden fields for a
+ * given capture will be available in its CaptureResult.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_OFF OFF}</li>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY SENSOR_SENSITIVITY_PRIORITY}</li>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY SENSOR_EXPOSURE_TIME_PRIORITY}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see #CONTROL_AE_PRIORITY_MODE_OFF
+ * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY
+ * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final Key<Integer> CONTROL_AE_PRIORITY_MODE =
+ new Key<Integer>("android.control.aePriorityMode", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -3489,7 +3570,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* duration exposed to the nearest possible value (rather than expose longer).
* The final exposure time used will be available in the output capture result.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
- * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the
+ * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_EXPOSURE_TIME_PRIORITY, this
+ * control will be effective and not controlled by the auto-exposure algorithm.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
* {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</p>
@@ -3499,6 +3582,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
@@ -3607,7 +3691,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* value. The final sensitivity used will be available in the
* output capture result.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
- * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the
+ * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_SENSITIVITY_PRIORITY, this
+ * control will be effective and not controlled by the auto-exposure algorithm.</p>
* <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
* to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
* {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
@@ -3623,6 +3709,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index bf3a072ff097..a52be973f564 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;
@@ -808,7 +807,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* application's selected exposure time, sensor sensitivity,
* and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
* {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
- * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is
+ * enabled, the relevant priority CaptureRequest settings will not be overridden.
+ * See {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} for more details. If one of the FLASH modes
* is selected, the camera device's flash unit controls are
* also overridden.</p>
* <p>The FLASH modes are only available if the camera device
@@ -842,6 +843,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureRequest#FLASH_MODE
@@ -2915,6 +2917,85 @@ 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>Turn on AE priority mode.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is
+ * AUTO and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to one of its
+ * ON modes, with the exception of ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+ * <p>When a priority mode is enabled, the camera device's
+ * auto-exposure routine will maintain the application's
+ * selected parameters relevant to the priority mode while overriding
+ * the remaining exposure parameters
+ * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). For example, if
+ * SENSOR_SENSITIVITY_PRIORITY mode is enabled, the camera device will
+ * maintain the application-selected {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}
+ * while adjusting {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}
+ * and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}. The overridden fields for a
+ * given capture will be available in its CaptureResult.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_OFF OFF}</li>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY SENSOR_SENSITIVITY_PRIORITY}</li>
+ * <li>{@link #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY SENSOR_EXPOSURE_TIME_PRIORITY}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see #CONTROL_AE_PRIORITY_MODE_OFF
+ * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY
+ * @see #CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY
+ */
+ @PublicKey
+ @NonNull
+ @FlaggedApi(Flags.FLAG_AE_PRIORITY)
+ public static final Key<Integer> CONTROL_AE_PRIORITY_MODE =
+ new Key<Integer>("android.control.aePriorityMode", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -4199,7 +4280,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* duration exposed to the nearest possible value (rather than expose longer).
* The final exposure time used will be available in the output capture result.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
- * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the
+ * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_EXPOSURE_TIME_PRIORITY, this
+ * control will be effective and not controlled by the auto-exposure algorithm.</p>
* <p><b>Units</b>: Nanoseconds</p>
* <p><b>Range of valid values:</b><br>
* {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</p>
@@ -4209,6 +4292,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
@@ -4317,7 +4401,9 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* value. The final sensitivity used will be available in the
* output capture result.</p>
* <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
- * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+ * OFF; otherwise the auto-exposure algorithm will override this value. However, in the
+ * case that {@link CaptureRequest#CONTROL_AE_PRIORITY_MODE android.control.aePriorityMode} is set to SENSOR_SENSITIVITY_PRIORITY, this
+ * control will be effective and not controlled by the auto-exposure algorithm.</p>
* <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
* to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
* {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
@@ -4333,6 +4419,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRIORITY_MODE
* @see CaptureRequest#CONTROL_MODE
* @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index e583627c0960..8b4d0da147bc 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -172,6 +172,23 @@ public final class DeviceState {
*/
public static final int PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT = 17;
+ /**
+ * Property that indicates that this state corresponds to the device state for rear display
+ * mode, where both the inner and outer displays are on. In this state, the outer display
+ * is the default display where the app is shown, and the inner display is used by the system to
+ * show a UI affordance for exiting the mode.
+ *
+ * Note that this value should generally not be used, and may be removed in the future (e.g.
+ * if or when it becomes the only type of rear display mode when
+ * {@link android.hardware.devicestate.feature.flags.Flags#deviceStateRdmV2} is removed).
+ *
+ * As such, clients should strongly consider relying on {@link #PROPERTY_FEATURE_REAR_DISPLAY}
+ * instead.
+ *
+ * @hide
+ */
+ public static final int PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT = 1001;
+
/** @hide */
@IntDef(prefix = {"PROPERTY_"}, flag = false, value = {
PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
@@ -190,7 +207,8 @@ public final class DeviceState {
PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE,
PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY,
PROPERTY_FEATURE_REAR_DISPLAY,
- PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT
+ PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 98ba9192044d..6230f4dbf6f4 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -29,4 +29,13 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "device_state_rdm_v2"
+ is_exported: true
+ namespace: "windowing_sdk"
+ description: "Enables Rear Display Mode V2, where the inner display shows the user a UI affordance for exiting the state"
+ bug: "372486634"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 506a19cce159..24951c4d516e 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -119,6 +119,8 @@ public final class KeyGestureEvent {
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 KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION = 74;
+ public static final int KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK = 75;
public static final int FLAG_CANCELLED = 1;
@@ -207,6 +209,8 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+ KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -781,6 +785,10 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
+ case KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
+ return "KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION";
+ case KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
+ return "KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK";
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 a8eb11d88aa4..4b2f2c218e5a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -153,8 +153,8 @@ flag {
flag {
name: "override_power_key_behavior_in_focused_window"
- namespace: "input_native"
- description: "Allows privileged focused windows to capture power key events."
+ namespace: "wallet_integration"
+ description: "Allows privileged focused windows to override the power key double tap behavior."
bug: "357144512"
}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 92608d048135..d2e232a94622 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -54,6 +54,11 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -823,6 +828,216 @@ public class UsbManager {
}
}
+ /**
+ * Opens the handle for accessory, marks it as input or output, and adds it to the map
+ * if it is the first time the accessory has had an I/O stream associated with it.
+ */
+ private AccessoryHandle openHandleForAccessory(UsbAccessory accessory,
+ boolean openingInputStream)
+ throws RemoteException {
+ synchronized (mAccessoryHandleMapLock) {
+ if (mAccessoryHandleMap == null) {
+ mAccessoryHandleMap = new ArrayMap<>();
+ }
+
+ // If accessory isn't available in map
+ if (!mAccessoryHandleMap.containsKey(accessory)) {
+ // open accessory and store associated AccessoryHandle in map
+ ParcelFileDescriptor pfd = mService.openAccessory(accessory);
+ AccessoryHandle newHandle = new AccessoryHandle(pfd, openingInputStream,
+ !openingInputStream);
+ mAccessoryHandleMap.put(accessory, newHandle);
+
+ return newHandle;
+ }
+
+ // if accessory is already in map, get modified handle
+ AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory);
+ if (currentHandle == null) {
+ throw new IllegalStateException("Accessory doesn't have an associated handle yet!");
+ }
+
+ AccessoryHandle modifiedHandle = getModifiedHandleForOpeningStream(
+ openingInputStream, currentHandle);
+
+ mAccessoryHandleMap.put(accessory, modifiedHandle);
+
+ return modifiedHandle;
+ }
+ }
+
+ private AccessoryHandle getModifiedHandleForOpeningStream(boolean openingInputStream,
+ @NonNull AccessoryHandle currentHandle) {
+ if (currentHandle.isInputStreamOpened() && openingInputStream) {
+ throw new IllegalStateException("Input stream already open for this accessory! "
+ + "Please close the existing input stream before opening a new one.");
+ }
+
+ if (currentHandle.isOutputStreamOpened() && !openingInputStream) {
+ throw new IllegalStateException("Output stream already open for this accessory! "
+ + "Please close the existing output stream before opening a new one.");
+ }
+
+ boolean isInputStreamOpened = openingInputStream || currentHandle.isInputStreamOpened();
+ boolean isOutputStreamOpened = !openingInputStream || currentHandle.isOutputStreamOpened();
+
+ return new AccessoryHandle(
+ currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened);
+ }
+
+ /**
+ * Marks the handle for the given accessory closed for input or output, and closes the handle
+ * and removes it from the map if there are no more I/O streams associated with the handle.
+ */
+ private void closeHandleForAccessory(UsbAccessory accessory, boolean closingInputStream)
+ throws IOException {
+ synchronized (mAccessoryHandleMapLock) {
+ AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory);
+
+ if (currentHandle == null) {
+ throw new IllegalStateException(
+ "No handle has been initialised for this accessory!");
+ }
+
+ AccessoryHandle modifiedHandle = getModifiedHandleForClosingStream(
+ closingInputStream, currentHandle);
+ if (!modifiedHandle.isOpen()) {
+ //close handle and remove accessory handle pair from map
+ modifiedHandle.getPfd().close();
+ mAccessoryHandleMap.remove(accessory);
+ } else {
+ mAccessoryHandleMap.put(accessory, modifiedHandle);
+ }
+ }
+ }
+
+ private AccessoryHandle getModifiedHandleForClosingStream(boolean closingInputStream,
+ @NonNull AccessoryHandle currentHandle) {
+ if (!currentHandle.isInputStreamOpened() && closingInputStream) {
+ throw new IllegalStateException(
+ "Attempting to close an input stream that has not been opened "
+ + "for this accessory!");
+ }
+
+ if (!currentHandle.isOutputStreamOpened() && !closingInputStream) {
+ throw new IllegalStateException(
+ "Attempting to close an output stream that has not been opened "
+ + "for this accessory!");
+ }
+
+ boolean isInputStreamOpened = !closingInputStream && currentHandle.isInputStreamOpened();
+ boolean isOutputStreamOpened = closingInputStream && currentHandle.isOutputStreamOpened();
+
+ return new AccessoryHandle(
+ currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened);
+ }
+
+ /**
+ * An InputStream you can create on a UsbAccessory, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
+ */
+ private class AccessoryAutoCloseInputStream extends FileInputStream {
+
+ private final ParcelFileDescriptor mPfd;
+ private final UsbAccessory mAccessory;
+
+ AccessoryAutoCloseInputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) {
+ super(pfd.getFileDescriptor());
+ this.mAccessory = accessory;
+ this.mPfd = pfd;
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* TODO(b/377850642) : Ensure the stream is closed even if client does not
+ explicitly close the stream to avoid corrupt FDs*/
+ super.close();
+ closeHandleForAccessory(mAccessory, true);
+ }
+
+
+ @Override
+ public int read() throws IOException {
+ final int result = super.read();
+ checkError(result);
+ return result;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ final int result = super.read(b);
+ checkError(result);
+ return result;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ final int result = super.read(b, off, len);
+ checkError(result);
+ return result;
+ }
+
+ private void checkError(int result) throws IOException {
+ if (result == -1 && mPfd.canDetectErrors()) {
+ mPfd.checkError();
+ }
+ }
+ }
+
+ /**
+ * An OutputStream you can create on a UsbAccessory, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
+ */
+ private class AccessoryAutoCloseOutputStream extends FileOutputStream {
+ private final UsbAccessory mAccessory;
+
+ AccessoryAutoCloseOutputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) {
+ super(pfd.getFileDescriptor());
+ mAccessory = accessory;
+ }
+
+ @Override
+ public void close() throws IOException {
+ /* TODO(b/377850642) : Ensure the stream is closed even if client does not
+ explicitly close the stream to avoid corrupt FDs*/
+ super.close();
+ closeHandleForAccessory(mAccessory, false);
+ }
+ }
+
+ /**
+ * Holds file descriptor and marks whether input and output streams have been opened for it.
+ */
+ private static class AccessoryHandle {
+ private final ParcelFileDescriptor mPfd;
+ private final boolean mInputStreamOpened;
+ private final boolean mOutputStreamOpened;
+ AccessoryHandle(ParcelFileDescriptor parcelFileDescriptor,
+ boolean inputStreamOpened, boolean outputStreamOpened) {
+ mPfd = parcelFileDescriptor;
+ mInputStreamOpened = inputStreamOpened;
+ mOutputStreamOpened = outputStreamOpened;
+ }
+
+ public ParcelFileDescriptor getPfd() {
+ return mPfd;
+ }
+
+ public boolean isInputStreamOpened() {
+ return mInputStreamOpened;
+ }
+
+ public boolean isOutputStreamOpened() {
+ return mOutputStreamOpened;
+ }
+
+ public boolean isOpen() {
+ return (mInputStreamOpened || mOutputStreamOpened);
+ }
+ }
+
private final Context mContext;
private final IUsbManager mService;
private final Object mDisplayPortListenersLock = new Object();
@@ -831,6 +1046,11 @@ public class UsbManager {
@GuardedBy("mDisplayPortListenersLock")
private DisplayPortAltModeInfoDispatchingListener mDisplayPortServiceListener;
+ private final Object mAccessoryHandleMapLock = new Object();
+ @GuardedBy("mAccessoryHandleMapLock")
+ private ArrayMap<UsbAccessory, AccessoryHandle> mAccessoryHandleMap;
+
+
/**
* @hide
*/
@@ -922,6 +1142,10 @@ public class UsbManager {
* data of a USB transfer should be read at once. If only a partial request is read the rest of
* the transfer is dropped.
*
+ * <p>It is strongly recommended to use newer methods instead of this method,
+ * since this method may provide sub-optimal performance on some devices.
+ * This method could potentially face interim performance degradation as well.
+ *
* @param accessory the USB accessory to open
* @return file descriptor, or null if the accessory could not be opened.
*/
@@ -935,6 +1159,49 @@ public class UsbManager {
}
/**
+ * Opens an input stream for reading from the USB accessory.
+ * If accessory is not open at this point, accessory will first be opened.
+ * <p>If data is read from the created {@link java.io.InputStream} all
+ * data of a USB transfer should be read at once. If only a partial request is read, the rest of
+ * the transfer is dropped.
+ * <p>The caller is responsible for ensuring that the returned stream is closed.
+ *
+ * @param accessory the USB accessory to open an input stream for
+ * @return input stream to read from given USB accessory
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+ public @NonNull InputStream openAccessoryInputStream(@NonNull UsbAccessory accessory) {
+ try {
+ return new AccessoryAutoCloseInputStream(accessory,
+ openHandleForAccessory(accessory, true).getPfd());
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Opens an output stream for writing to the USB accessory.
+ * If accessory is not open at this point, accessory will first be opened.
+ * <p>The caller is responsible for ensuring that the returned stream is closed.
+ *
+ * @param accessory the USB accessory to open an output stream for
+ * @return output stream to write to given USB accessory
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+ public @NonNull OutputStream openAccessoryOutputStream(@NonNull UsbAccessory accessory) {
+ try {
+ return new AccessoryAutoCloseOutputStream(accessory,
+ openHandleForAccessory(accessory, false).getPfd());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
* Gets the functionfs control file descriptor for the given function, with
* the usb descriptors and strings already written. The file descriptor is used
* by the function implementation to handle events and control requests.
@@ -1293,7 +1560,7 @@ public class UsbManager {
* <p>
* This function returns the current USB bandwidth through USB Gadget HAL.
* It should be used when Android device is in USB peripheral mode and
- * connects to a USB host. If USB state is not configued, API will return
+ * connects to a USB host. If USB state is not configured, API will return
* {@value #USB_DATA_TRANSFER_RATE_UNKNOWN}. In addition, the unit of the
* return value is Mbps.
* </p>
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 3b7a9e95c521..b719a7c6daac 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -31,3 +31,11 @@ flag {
description: "Feature flag to enable exposing usb speed system api"
bug: "373653182"
}
+
+flag {
+ name: "enable_accessory_stream_api"
+ is_exported: true
+ namespace: "usb"
+ description: "Feature flag to enable stream APIs for Accessory mode"
+ bug: "369356693"
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef08039..ae8366817f2b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
import static android.view.inputmethod.Flags.ctrlShiftShortcut;
import static android.view.inputmethod.Flags.predictiveBackIme;
@@ -4392,6 +4393,39 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Called when the requested visibility of a custom IME Switcher button changes.
+ *
+ * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+ * button inside this bar. However, the IME can request hiding the bar provided by the system
+ * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+ * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+ * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+ * input view, with equivalent functionality.</p>
+ *
+ * <p>This custom button is only requested to be visible when the system provides the IME
+ * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+ * but the IME successfully requested to hide the bar. This does not depend on the current
+ * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+ * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+ *
+ * <p>This is only called when the requested visibility changes. The default value is
+ * {@code false} and as such, this will not be called initially if the resulting value is
+ * {@code false}.</p>
+ *
+ * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+ * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+ * on when the IME requested hiding the IME navigation bar. If the request is sent during
+ * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+ * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+ *
+ * @param visible whether the button is requested visible or not.
+ */
+ @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+ public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+ // Intentionally empty
+ }
+
+ /**
* Called when the IME switch button was clicked from the client. Depending on the number of
* enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
* method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454dd7f8f..38be8d9f772d 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@ import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -178,6 +179,9 @@ final class NavigationBarController {
private boolean mDrawLegacyNavigationBarBackground;
+ /** Whether a custom IME Switcher button should be visible. */
+ private boolean mCustomImeSwitcherVisible;
+
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
@@ -265,6 +269,7 @@ final class NavigationBarController {
// IME navigation bar.
boolean visible = insets.isVisible(captionBar());
mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+ checkCustomImeSwitcherVisibility();
}
return view.onApplyWindowInsets(insets);
});
@@ -491,6 +496,8 @@ final class NavigationBarController {
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ checkCustomImeSwitcherVisibility();
+
mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
.setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
@@ -616,12 +623,33 @@ final class NavigationBarController {
&& mNavigationBarFrame.getVisibility() == View.VISIBLE;
}
+ /**
+ * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+ * state changes. This can only be {@code true} if three conditions are met:
+ *
+ * <li>The IME should draw the IME navigation bar.</li>
+ * <li>The IME Switcher button should be visible when the IME is visible.</li>
+ * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+ */
+ private void checkCustomImeSwitcherVisibility() {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return;
+ }
+ final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+ && mNavigationBarFrame != null && !isShown();
+ if (visible != mCustomImeSwitcherVisible) {
+ mCustomImeSwitcherVisible = visible;
+ mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+ }
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ " mShouldShowImeSwitcherWhenImeIsShown="
+ mShouldShowImeSwitcherWhenImeIsShown
+ + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible
+ " mAppearance=0x" + Integer.toHexString(mAppearance)
+ " mDarkIntensity=" + mDarkIntensity
+ " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 6d4e28403908..517418a717fb 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1011,13 +1011,7 @@ public class WorkSource implements Parcelable {
return mTags.length > 0 ? mTags[0] : null;
}
- // TODO: The following three trivial getters are purely for testing and will be removed
- // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
- // diffing it etc.
-
-
/** @hide */
- @VisibleForTesting
public int[] getUids() {
int[] uids = new int[mSize];
System.arraycopy(mUids, 0, uids, 0, mSize);
@@ -1025,7 +1019,6 @@ public class WorkSource implements Parcelable {
}
/** @hide */
- @VisibleForTesting
public String[] getTags() {
String[] tags = new String[mSize];
System.arraycopy(mTags, 0, tags, 0, mSize);
@@ -1033,7 +1026,6 @@ public class WorkSource implements Parcelable {
}
/** @hide */
- @VisibleForTesting
public int getSize() {
return mSize;
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 9c83bc2c88ec..d9db28e0b3c3 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -243,6 +243,15 @@ flag {
}
flag {
+ name: "update_engine_api"
+ namespace: "art_mainline"
+ description: "Update Engine APIs for ART"
+ is_exported: true
+ is_fixed_read_only: true
+ bug: "377557749"
+}
+
+flag {
namespace: "system_performance"
name: "perfetto_sdk_tracing"
description: "Tracing using Perfetto SDK."
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 60a0ae3f107d..92c5c20a1f82 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -386,3 +386,17 @@ flag {
description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases."
bug: "370977414"
}
+
+flag {
+ name: "system_selection_toolbar_enabled"
+ namespace: "permissions"
+ description: "Enables the system selection toolbar feature."
+ bug: "363318732"
+}
+
+flag {
+ name: "use_system_selection_toolbar_in_sysui"
+ namespace: "permissions"
+ description: "Uses the SysUi process to host the SelectionToolbarRenderService."
+ bug: "363318732"
+}
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 7cb0ffcfcc72..ce901217d700 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -109,7 +109,7 @@ flag {
flag {
name: "afl_api"
- namespace: "platform_security"
+ namespace: "hardware_backed_security"
description: "AFL feature"
bug: "365994454"
}
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/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/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 02923eda308e..f43f172d7d5b 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -163,10 +163,12 @@ flag {
}
flag {
- name: "typeface_redesign"
+ name: "typeface_redesign_readonly"
namespace: "text"
description: "Decouple variation settings, weight and style information from Typeface class"
bug: "361260253"
+ # This feature does not support runtime flag switch which leads crash in System UI.
+ is_fixed_read_only: true
}
flag {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9a2aa0b8a682..75d2da1b70e4 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -9951,11 +9951,13 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- if (!mIsDrawing) {
- destroyHardwareRenderer();
- } else {
- Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
- " window=" + this + ", title=" + mWindowAttributes.getTitle());
+ if (!com.android.graphics.hwui.flags.Flags.removeVriSketchyDestroy()) {
+ if (!mIsDrawing) {
+ destroyHardwareRenderer();
+ } else {
+ Log.e(mTag, "Attempting to destroy the window while drawing!\n"
+ + " window=" + this + ", title=" + mWindowAttributes.getTitle());
+ }
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
@@ -9976,9 +9978,9 @@ public final class ViewRootImpl implements ViewParent,
dispatchDetachedFromWindow();
}
- if (mAdded && !mFirst) {
- destroyHardwareRenderer();
+ destroyHardwareRenderer();
+ if (mAdded && !mFirst) {
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 0ab51e45a951..905f350ca6c5 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -316,6 +316,35 @@ public class AutofillFeatureFlags {
// END AUTOFILL PCC CLASSIFICATION FLAGS
+ // START AUTOFILL REMOVE PRE_TRIGGER FLAGS
+
+ /**
+ * Whether pre-trigger flow is disabled.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IMPROVE_FILL_DIALOG_ENABLED = "improve_fill_dialog";
+
+ /**
+ * Minimum amount of time (in milliseconds) to wait after IME animation finishes, and before
+ * starting fill dialog animation.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS =
+ "fill_dialog_min_wait_after_animation_end_ms";
+
+ /**
+ * Sets a value of timeout in milliseconds, measured after animation end, during which fill
+ * dialog can be shown. If we are at time > animation_end_time + this timeout, fill dialog
+ * wouldn't be shown.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_FILL_DIALOG_TIMEOUT_MS = "fill_dialog_timeout_ms";
+
+ // END AUTOFILL REMOVE PRE_TRIGGER FLAGS
+
/**
* Define the max input length for autofill to show suggesiton UI
*
@@ -366,6 +395,17 @@ public class AutofillFeatureFlags {
DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true;
// END AUTOFILL FOR ALL APPS DEFAULTS
+ // START AUTOFILL REMOVE PRE_TRIGGER FLAGS DEFAULTS
+ // Default for whether the pre trigger removal is enabled.
+ /** @hide */
+ public static final boolean DEFAULT_IMPROVE_FILL_DIALOG_ENABLED = true;
+ // Default for whether the pre trigger removal is enabled.
+ /** @hide */
+ public static final long DEFAULT_FILL_DIALOG_TIMEOUT_MS = 300; // 300 ms
+ /** @hide */
+ public static final long DEFAULT_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS = 0; // 0 ms
+ // END AUTOFILL REMOVE PRE_TRIGGER FLAGS DEFAULTS
+
/**
* @hide
*/
@@ -611,4 +651,48 @@ public class AutofillFeatureFlags {
}
// END AUTOFILL PCC CLASSIFICATION FUNCTIONS
+
+
+ // START AUTOFILL REMOVE PRE_TRIGGER
+ /**
+ * Whether Autofill Pre Trigger Removal is enabled.
+ *
+ * @hide
+ */
+ public static boolean isImproveFillDialogEnabled() {
+ // TODO(b/266379948): Add condition for checking whether device has PCC first
+
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IMPROVE_FILL_DIALOG_ENABLED,
+ DEFAULT_IMPROVE_FILL_DIALOG_ENABLED);
+ }
+
+ /**
+ * Whether Autofill Pre Trigger Removal is enabled.
+ *
+ * @hide
+ */
+ public static long getFillDialogTimeoutMs() {
+ // TODO(b/266379948): Add condition for checking whether device has PCC first
+
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_FILL_DIALOG_TIMEOUT_MS,
+ DEFAULT_FILL_DIALOG_TIMEOUT_MS);
+ }
+
+ /**
+ * Whether Autofill Pre Trigger Removal is enabled.
+ *
+ * @hide
+ */
+ public static long getFillDialogMinWaitAfterImeAnimationtEndMs() {
+ // TODO(b/266379948): Add condition for checking whether device has PCC first
+
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS,
+ DEFAULT_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS);
+ }
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index dae87ddcb1bd..7a01ad340c56 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -54,6 +54,7 @@ public enum DesktopModeFlags {
Flags::enableDesktopWindowingWallpaperActivity, true),
ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+ ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
@@ -75,7 +76,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/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index d39ecabbb2d2..f474b34ac390 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -353,6 +353,16 @@ flag {
}
flag {
+ name: "enable_desktop_system_dialogs_transitions"
+ namespace: "lse_desktop_experience"
+ description: "Enables custom transitions for system dialogs in Desktop Mode."
+ bug: "335638193"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_move_to_next_display_shortcut"
namespace: "lse_desktop_experience"
description: "Add new keyboard shortcut of moving a task into next display"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 68e78fed29c5..d9de38a8bd34 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -268,6 +268,16 @@ flag {
}
flag {
+ name: "system_ui_post_animation_end"
+ namespace: "windowing_frontend"
+ description: "Run AnimatorListener#onAnimationEnd on next frame for SystemUI"
+ bug: "300035126"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "system_ui_immersive_confirmation_dialog"
namespace: "windowing_frontend"
description: "Enable the implementation of the immersive confirmation dialog on system UI side by default"
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 44dceb9b7edb..4a49bb6720ef 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -63,6 +63,8 @@ public final class ShortcutConstants {
* quickly tapping screen 2 times with two fingers as preferred shortcut.
* {@code QUICK_SETTINGS} for displaying specifying the accessibility services or features which
* choose Quick Settings as preferred shortcut.
+ * {@code KEY_GESTURE} for shortcuts which are directly from key gestures and should be
+ * activated always.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -73,6 +75,7 @@ public final class ShortcutConstants {
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
UserShortcutType.GESTURE,
+ UserShortcutType.KEY_GESTURE,
UserShortcutType.ALL
})
public @interface UserShortcutType {
@@ -84,8 +87,10 @@ public final class ShortcutConstants {
int TWOFINGER_DOUBLETAP = 1 << 3;
int QUICK_SETTINGS = 1 << 4;
int GESTURE = 1 << 5;
+ int KEY_GESTURE = 1 << 6;
// LINT.ThenChange(:shortcut_type_array)
- int ALL = SOFTWARE | HARDWARE | TRIPLETAP | TWOFINGER_DOUBLETAP | QUICK_SETTINGS | GESTURE;
+ int ALL = SOFTWARE | HARDWARE | TRIPLETAP | TWOFINGER_DOUBLETAP | QUICK_SETTINGS | GESTURE
+ | KEY_GESTURE;
}
/**
@@ -99,7 +104,8 @@ public final class ShortcutConstants {
UserShortcutType.TRIPLETAP,
UserShortcutType.TWOFINGER_DOUBLETAP,
UserShortcutType.QUICK_SETTINGS,
- UserShortcutType.GESTURE
+ UserShortcutType.GESTURE,
+ UserShortcutType.KEY_GESTURE
// LINT.ThenChange(:shortcut_type_intdef)
};
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 2e0ff3db6c50..14ca0f8cae69 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -27,6 +27,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.SERVIC
import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -187,6 +188,7 @@ public final class ShortcutUtils {
case TWOFINGER_DOUBLETAP ->
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
case QUICK_SETTINGS -> Settings.Secure.ACCESSIBILITY_QS_TARGETS;
+ case KEY_GESTURE -> Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS;
default -> throw new IllegalArgumentException(
"Unsupported user shortcut type: " + type);
};
@@ -209,6 +211,7 @@ public final class ShortcutUtils {
TRIPLETAP;
case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED ->
TWOFINGER_DOUBLETAP;
+ case Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS -> KEY_GESTURE;
default -> throw new IllegalArgumentException(
"Unsupported user shortcut key: " + key);
};
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 50252c11ffb1..42406147b2f0 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -538,7 +538,7 @@ static bool attributionSourceStateForJavaParcel(JNIEnv *env, jobject jClientAttr
return false;
}
- if (!(useContextAttributionSource && flags::use_context_attribution_source())) {
+ if (!(useContextAttributionSource && flags::data_delivery_permission_checks())) {
clientAttribution.uid = Camera::USE_CALLING_UID;
clientAttribution.pid = Camera::USE_CALLING_PID;
}
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/res/values/config.xml b/core/res/res/values/config.xml
index 6682b858cce9..dc054a4a48ea 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4596,6 +4596,11 @@
exists on the device, the accessibility shortcut will be disabled by default. -->
<string name="config_defaultAccessibilityService" translatable="false"></string>
+ <!-- The component name, flattened to a string, for the default select to speak service to be
+ enabled by the accessibility keyboard shortcut. If the service with the specified component
+ name is not preinstalled then this shortcut will do nothing. -->
+ <string name="config_defaultSelectToSpeakService" translatable="false"></string>
+
<!-- URI for default Accessibility notification sound when to enable accessibility shortcut. -->
<string name="config_defaultAccessibilityNotificationSound" translatable="false"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index db81a3be440f..badb98686fb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3718,6 +3718,7 @@
<java-symbol type="string" name="color_correction_feature_name" />
<java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
+ <java-symbol type="string" name="config_defaultSelectToSpeakService" />
<java-symbol type="string" name="config_defaultAccessibilityNotificationSound" />
<java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
<java-symbol type="array" name="config_trustedAccessibilityServices" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a382d798fb9b..f39508d6de15 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -151,6 +151,7 @@ android_test {
":HelloWorldUsingSdk1And2",
":HelloWorldUsingSdkMalformedNegativeVersion",
":CtsStaticSharedLibConsumerApp1",
+ ":CtsStaticSharedLibConsumerApp3",
],
}
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 3f7c83a82787..5d8ff87eca24 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -41,6 +41,8 @@
value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
<option name="push-file" key="CtsStaticSharedLibConsumerApp1.apk"
value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp1.apk"/>
+ <option name="push-file" key="CtsStaticSharedLibConsumerApp3.apk"
+ value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp3.apk"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index d4618d744644..0db49a72c51d 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -72,6 +72,12 @@ public class ApkLiteParseUtilsTest {
private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
"HelloWorldUsingSdkMalformedNegativeVersion.apk";
private static final String TEST_APP_USING_STATIC_LIB = "CtsStaticSharedLibConsumerApp1.apk";
+ private static final String TEST_APP_USING_STATIC_LIB_TWO_CERTS =
+ "CtsStaticSharedLibConsumerApp3.apk";
+ private static final String STATIC_LIB_CERT_1 =
+ "70fbd440503ec0bf41f3f21fcc83ffd39880133c27deb0945ed677c6f31d72fb";
+ private static final String STATIC_LIB_CERT_2 =
+ "e49582ff3a0aa4c5589fc5feaac6b7d6e757199dd0c6742df7bf37c2ffef95f5";
private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
private static final String TEST_SDK1_NAME = "com.test.sdk1";
@@ -86,7 +92,7 @@ public class ApkLiteParseUtilsTest {
@Before
public void setUp() throws IOException {
- mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+ mTmpDir = mTemporaryFolder.newFolder("ApkLiteParseUtilsTest");
}
@After
@@ -108,9 +114,8 @@ public class ApkLiteParseUtilsTest {
assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
TEST_SDK1_VERSION, TEST_SDK2_VERSION
);
- for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
- assertThat(certDigests).asList().containsExactly("");
- }
+ String[][] expectedCerts = {{""}, {""}};
+ assertThat(baseApk.getUsesSdkLibrariesCertDigests()).isEqualTo(expectedCerts);
}
@SuppressLint("CheckResult")
@@ -126,18 +131,13 @@ public class ApkLiteParseUtilsTest {
ApkLite baseApk = result.getResult();
String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
- assertThat(liteCerts).isNotNull();
- for (String[] certDigests: liteCerts) {
- assertThat(certDigests).asList().containsExactly(certDigest);
- }
+ String[][] expectedCerts = {{certDigest}, {certDigest}};
+ assertThat(liteCerts).isEqualTo(expectedCerts);
// Same for package parser
AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
- assertThat(pkgCerts).isNotNull();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@@ -160,9 +160,7 @@ public class ApkLiteParseUtilsTest {
String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@SuppressLint("CheckResult")
@@ -184,9 +182,27 @@ public class ApkLiteParseUtilsTest {
String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
- for (int i = 0; i < liteCerts.length; i++) {
- assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
- }
+ assertThat(liteCerts).isEqualTo(pkgCerts);
+ }
+
+ @Test
+ public void testParseApkLite_getUsesStaticLibrary_twoCerts()
+ throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_STATIC_LIB_TWO_CERTS);
+ ParseResult<ApkLite> result = ApkLiteParseUtils
+ .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ // There are two certs.
+ String[][] expectedCerts = {{STATIC_LIB_CERT_1, STATIC_LIB_CERT_2}};
+ String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
+ assertThat(liteCerts).isEqualTo(expectedCerts);
+
+ // And they are same as package parser.
+ AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+ String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
+ assertThat(liteCerts).isEqualTo(pkgCerts);
}
@SuppressLint("CheckResult")
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 8bebc62e93f2..1a9af6b55eed 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAG
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.google.common.truth.Truth.assertThat;
@@ -123,6 +124,14 @@ public class ShortcutUtilsTest {
}
@Test
+ public void getShortcutTargets_keyGestureShortcutNoService_emptyResult() {
+ assertThat(
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext, KEY_GESTURE, mDefaultUserId)
+ ).isEmpty();
+ }
+
+ @Test
public void getShortcutTargets_softwareShortcut1Service_return1Service() {
setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
setupShortcutTargets(TWO_COMPONENTS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 7ced809d2a3a..541ca602a386 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,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/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 8bb32568ec5a..56bb0f0d12d5 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2119,7 +2119,7 @@ public class Paint {
* @see FontVariationAxis
*/
public boolean setFontVariationSettings(String fontVariationSettings) {
- final boolean useFontVariationStore = Flags.typefaceRedesign()
+ final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
&& CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
if (useFontVariationStore) {
FontVariationAxis[] axes =
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index ed17fdefcb53..43216ba6e087 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -133,7 +133,7 @@ public final class PositionedGlyphs {
@NonNull
public Font getFont(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
- if (Flags.typefaceRedesign()) {
+ if (Flags.typefaceRedesignReadonly()) {
return mFonts.get(nGetFontId(mLayoutPtr, index));
}
return mFonts.get(index);
@@ -252,7 +252,7 @@ public final class PositionedGlyphs {
mXOffset = xOffset;
mYOffset = yOffset;
- if (Flags.typefaceRedesign()) {
+ if (Flags.typefaceRedesignReadonly()) {
int fontCount = nGetFontCount(layoutPtr);
mFonts = new ArrayList<>(fontCount);
for (int i = 0; i < fontCount; ++i) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 4d7be39ca5a4..76eb207a31c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -19,6 +19,7 @@ package androidx.window.extensions.area;
import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@@ -104,6 +105,30 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int mLastReportedRearDisplayPresentationStatus;
+ @VisibleForTesting
+ static int getRdmV1Identifier(List<DeviceState> currentSupportedDeviceStates) {
+ for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+ DeviceState state = currentSupportedDeviceStates.get(i);
+ if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)
+ && !state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+ return state.getIdentifier();
+ }
+ }
+ return INVALID_DEVICE_STATE_IDENTIFIER;
+ }
+
+ @VisibleForTesting
+ static int getRdmV2Identifier(List<DeviceState> currentSupportedDeviceStates) {
+ for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+ DeviceState state = currentSupportedDeviceStates.get(i);
+ if (state.hasProperties(PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+ return state.getIdentifier();
+ }
+ }
+ return INVALID_DEVICE_STATE_IDENTIFIER;
+ }
+
public WindowAreaComponentImpl(@NonNull Context context) {
mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
mDisplayManager = context.getSystemService(DisplayManager.class);
@@ -112,12 +137,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedDeviceStates();
if (Flags.deviceStatePropertyMigration()) {
- for (int i = 0; i < mCurrentSupportedDeviceStates.size(); i++) {
- DeviceState state = mCurrentSupportedDeviceStates.get(i);
- if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)) {
- mRearDisplayState = state.getIdentifier();
- break;
- }
+ if (Flags.deviceStateRdmV2()) {
+ mRearDisplayState = getRdmV2Identifier(mCurrentSupportedDeviceStates);
+ } else {
+ mRearDisplayState = getRdmV1Identifier(mCurrentSupportedDeviceStates);
}
} else {
mFoldedDeviceStates = context.getResources().getIntArray(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
index ccb4ebe9199e..d677fef5c22c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
@@ -16,8 +16,13 @@
package androidx.window.extensions.area;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
import static org.junit.Assert.assertEquals;
+import android.hardware.devicestate.DeviceState;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.view.Surface;
@@ -29,11 +34,34 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WindowAreaComponentImplTests {
+ private static final DeviceState REAR_DISPLAY_STATE_V1 = new DeviceState(
+ new DeviceState.Configuration.Builder(1, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY))
+ .build());
+ private static final DeviceState REAR_DISPLAY_STATE_V2 = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+ .build());
+ // The PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT state must be present together with the
+ // PROPERTY_FEATURE_REAR_DISPLAY state in order to be a valid state.
+ private static final DeviceState INVALID_REAR_DISPLAY_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+ .build());
+
private final DisplayMetrics mTestDisplayMetrics = new DisplayMetrics();
@Before
@@ -93,4 +121,37 @@ public class WindowAreaComponentImplTests {
Surface.ROTATION_270, Surface.ROTATION_0, mTestDisplayMetrics);
assertEquals(expectedMetrics, mTestDisplayMetrics);
}
+
+ @Test
+ public void testRdmV1Identifier() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+ supportedStates.add(REAR_DISPLAY_STATE_V2);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+
+ supportedStates.add(REAR_DISPLAY_STATE_V1);
+ assertEquals(REAR_DISPLAY_STATE_V1.getIdentifier(),
+ WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+ }
+
+ @Test
+ public void testRdmV2Identifier_whenStateIsImproperlyConfigured() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+ supportedStates.add(INVALID_REAR_DISPLAY_STATE);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+ }
+
+ @Test
+ public void testRdmV2Identifier_whenStateIsProperlyConfigured() {
+ final List<DeviceState> supportedStates = new ArrayList<>();
+
+ supportedStates.add(REAR_DISPLAY_STATE_V1);
+ assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+
+ supportedStates.add(REAR_DISPLAY_STATE_V2);
+ assertEquals(REAR_DISPLAY_STATE_V2.getIdentifier(),
+ WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+ }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 0b515f590f98..5f42bb161204 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -475,6 +475,6 @@ class BubbleStackViewTest {
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 0d742cc6e382..6ac36a3319c9 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -375,7 +375,7 @@ class BubbleBarExpandedViewTest {
override fun hideCurrentInputMethod() {
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
}
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 00d9a931cebe..0044593ad228 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -351,7 +351,7 @@ class BubbleBarLayerViewTest {
override fun hideCurrentInputMethod() {}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+ override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
}
}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index f90e165ffc74..a18a2510f0f7 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -168,7 +168,7 @@
</LinearLayout>
<LinearLayout
- android:id="@+id/open_in_browser_pill"
+ android:id="@+id/open_in_app_or_browser_pill"
android:layout_width="match_parent"
android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
@@ -178,7 +178,7 @@
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
- android:id="@+id/open_in_browser_button"
+ android:id="@+id/open_in_app_or_browser_button"
android:layout_weight="1"
android:contentDescription="@string/open_in_browser_text"
android:text="@string/open_in_browser_text"
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 8f1ef6c7e49e..012579a6d40c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -301,6 +301,8 @@
<string name="screenshot_text">Screenshot</string>
<!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] -->
<string name="open_in_browser_text">Open in browser</string>
+ <!-- Accessibility text for the handle menu open in app button [CHAR LIMIT=NONE] -->
+ <string name="open_in_app_text">Open in App</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
<string name="new_window_text">New Window</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 191875d38daf..84a22b873aaf 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.shared.bubbles
+import android.annotation.IntDef
import android.os.Parcel
import android.os.Parcelable
@@ -60,4 +61,36 @@ enum class BubbleBarLocation : Parcelable {
override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
}
}
+
+ /** Define set of constants that allow to determine why location changed. */
+ @IntDef(
+ UpdateSource.DRAG_BAR,
+ UpdateSource.DRAG_BUBBLE,
+ UpdateSource.DRAG_EXP_VIEW,
+ UpdateSource.A11Y_ACTION_BAR,
+ UpdateSource.A11Y_ACTION_BUBBLE,
+ UpdateSource.A11Y_ACTION_EXP_VIEW,
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class UpdateSource {
+ companion object {
+ /** Location changed from dragging the bar */
+ const val DRAG_BAR = 1
+
+ /** Location changed from dragging the bubble */
+ const val DRAG_BUBBLE = 2
+
+ /** Location changed from dragging the expanded view */
+ const val DRAG_EXP_VIEW = 3
+
+ /** Location changed via a11y action on the bar */
+ const val A11Y_ACTION_BAR = 4
+
+ /** Location changed via a11y action on the bubble */
+ const val A11Y_ACTION_BUBBLE = 5
+
+ /** Location changed via a11y action on the expanded view */
+ const val A11Y_ACTION_EXP_VIEW = 6
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 65132fe89063..7243ea36b137 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -20,7 +20,9 @@ package com.android.wm.shell.apptoweb
import android.content.Context
import android.content.Intent
+import android.content.Intent.ACTION_VIEW
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER
import android.content.pm.PackageManager
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState
@@ -31,7 +33,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup
private const val TAG = "AppToWebUtils"
private val GenericBrowserIntent = Intent()
- .setAction(Intent.ACTION_VIEW)
+ .setAction(ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.parse("http:"))
@@ -67,6 +69,20 @@ fun getBrowserIntent(uri: Uri, packageManager: PackageManager): Intent? {
}
/**
+ * Returns intent if there is a non-browser application available to handle the uri. Otherwise,
+ * returns null.
+ */
+fun getAppIntent(uri: Uri, packageManager: PackageManager): Intent? {
+ val intent = Intent(ACTION_VIEW, uri).apply {
+ flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
+ }
+ // If there is no application available to handle intent, return null
+ val component = intent.resolveActivity(packageManager) ?: return null
+ intent.setComponent(component)
+ return intent
+}
+
+/**
* Returns the [DomainVerificationUserState] of the user associated with the given
* [DomainVerificationManager] and the given package.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index c92a2786e49b..ce7a97703f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1122,7 +1122,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
final BackMotionEvent backFinish = mCurrentTracker
.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backFinish);
- if (!mBackGestureStarted) {
+ if (mCurrentTracker.isFinished()) {
// if the down -> up gesture happened before animation
// start, we have to trigger the uninterruptible transition
// to finish the back animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 14f8cc74bfc5..0fd98ed7eaf1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -740,8 +740,10 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* Update bubble bar location and trigger and update to listeners
*/
- public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+ @BubbleBarLocation.UpdateSource int source) {
if (canShowAsBubbleBar()) {
+ BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
mLayerView.updateExpandedView();
@@ -749,13 +751,47 @@ public class BubbleController implements ConfigurationChangeListener,
BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+
+ logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
+ }
+ }
+
+ private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
+ BubbleBarLocation previous,
+ @BubbleBarLocation.UpdateSource int source) {
+ if (mLayerView == null) {
+ return;
+ }
+ boolean isRtl = mLayerView.isLayoutRtl();
+ boolean wasLeft = previous.isOnLeft(isRtl);
+ boolean onLeft = location.isOnLeft(isRtl);
+ if (wasLeft == onLeft) {
+ // No changes, skip logging
+ return;
+ }
+ switch (source) {
+ case BubbleBarLocation.UpdateSource.DRAG_BAR:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+ mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+ break;
+ case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
+ case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+ // TODO(b/349845968): move logging from BubbleBarLayerView to here
+ break;
}
}
/**
* Animate bubble bar to the given location. The location change is transient. It does not
* update the state of the bubble bar.
- * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+ * To update bubble bar pinned location, use
+ * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
*/
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
if (canShowAsBubbleBar()) {
@@ -2568,9 +2604,10 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void setBubbleBarLocation(BubbleBarLocation location) {
+ public void setBubbleBarLocation(BubbleBarLocation location,
+ @BubbleBarLocation.UpdateSource int source) {
mMainExecutor.execute(() ->
- mController.setBubbleBarLocation(location));
+ mController.setBubbleBarLocation(location, source));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index ec4854b47aff..6423eed59165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -32,7 +32,10 @@ interface BubbleExpandedViewManager {
fun isStackExpanded(): Boolean
fun isShowingAsBubbleBar(): Boolean
fun hideCurrentInputMethod()
- fun updateBubbleBarLocation(location: BubbleBarLocation)
+ fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ )
companion object {
/**
@@ -82,8 +85,11 @@ interface BubbleExpandedViewManager {
controller.hideCurrentInputMethod()
}
- override fun updateBubbleBarLocation(location: BubbleBarLocation) {
- controller.bubbleBarLocation = location
+ override fun updateBubbleBarLocation(
+ location: BubbleBarLocation,
+ @BubbleBarLocation.UpdateSource source: Int,
+ ) {
+ controller.setBubbleBarLocation(location, source)
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 1855b938f48e..9c2d35431554 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -44,7 +44,7 @@ interface IBubbles {
oneway void showUserEducation(in int positionX, in int positionY) = 8;
- oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+ oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9;
oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
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 272dfecb0bf9..3764bcd42ac6 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
@@ -637,11 +637,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
return true;
}
if (action == R.id.action_move_bubble_bar_left) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
if (action == R.id.action_move_bubble_bar_right) {
- mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+ mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
return true;
}
return false;
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 1f77abe54c8d..0c05e3c5115c 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
@@ -441,7 +441,8 @@ public class BubbleBarLayerView extends FrameLayout
@Override
public void onRelease(@NonNull BubbleBarLocation location) {
- mBubbleController.setBubbleBarLocation(location);
+ mBubbleController.setBubbleBarLocation(location,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
if (location != mInitialLocation) {
BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
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 44fce81fa059..601cf70b93ed 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
@@ -67,6 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopBackNavigationTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -915,6 +916,16 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static DesktopBackNavigationTransitionHandler provideDesktopBackNavigationTransitionHandler(
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellAnimationThread ShellExecutor animExecutor,
+ DisplayController displayController) {
+ return new DesktopBackNavigationTransitionHandler(mainExecutor, animExecutor,
+ displayController);
+ }
+
+ @WMSingleton
+ @Provides
static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
Transitions transitions) {
return new DesktopModeDragAndDropTransitionHandler(transitions);
@@ -964,6 +975,7 @@ public abstract class WMShellModule {
Optional<DesktopRepository> desktopRepository,
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
+ Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
ShellInit shellInit) {
return desktopRepository.flatMap(
repository ->
@@ -973,6 +985,7 @@ public abstract class WMShellModule {
repository,
transitions,
shellTaskOrganizer,
+ desktopMixedTransitionHandler.get(),
shellInit)));
}
@@ -985,6 +998,7 @@ public abstract class WMShellModule {
FreeformTaskTransitionHandler freeformTaskTransitionHandler,
CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
Optional<DesktopImmersiveController> desktopImmersiveController,
+ DesktopBackNavigationTransitionHandler desktopBackNavigationTransitionHandler,
InteractionJankMonitor interactionJankMonitor,
@ShellMainThread Handler handler,
ShellInit shellInit,
@@ -1001,6 +1015,7 @@ public abstract class WMShellModule {
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController.get(),
+ desktopBackNavigationTransitionHandler,
interactionJankMonitor,
handler,
shellInit,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
new file mode 100644
index 000000000000..83b0f8413a28
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.DisplayMetrics
+import android.view.SurfaceControl.Transaction
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.MinimizeAnimator.create
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [Transitions.TransitionHandler] that handles transitions for tasks that are closing or going
+ * to back as part of back navigation. This handler is used only for animating transitions.
+ */
+class DesktopBackNavigationTransitionHandler(
+ private val mainExecutor: ShellExecutor,
+ private val animExecutor: ShellExecutor,
+ private val displayController: DisplayController,
+) : Transitions.TransitionHandler {
+
+ /** Shouldn't handle anything */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ /** Animates a transition with minimizing tasks */
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ if (!TransitionUtil.isClosingType(info.type)) return false
+
+ val animations = mutableListOf<Animator>()
+ val onAnimFinish: (Animator) -> Unit = { animator ->
+ mainExecutor.execute {
+ // Animation completed
+ animations.remove(animator)
+ if (animations.isEmpty()) {
+ // All animations completed, finish the transition
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ }
+ }
+ }
+
+ animations +=
+ info.changes
+ .filter {
+ it.mode == info.type &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
+ if (animations.isEmpty()) return false
+ animExecutor.execute { animations.forEach(Animator::start) }
+ return true
+ }
+
+ private fun createMinimizeAnimation(
+ change: TransitionInfo.Change,
+ finishTransaction: Transaction,
+ onAnimFinish: (Animator) -> Unit
+ ): Animator? {
+ val t = Transaction()
+ val sc = change.leash
+ finishTransaction.hide(sc)
+ val displayMetrics: DisplayMetrics? =
+ change.taskInfo?.let {
+ displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics
+ }
+ return displayMetrics?.let { create(it, change, t, onAnimFinish) }
+ }
+}
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 01c680dc8325..2001f9743094 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
@@ -52,6 +52,7 @@ class DesktopMixedTransitionHandler(
private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
private val desktopImmersiveController: DesktopImmersiveController,
+ private val desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler,
private val interactionJankMonitor: InteractionJankMonitor,
@ShellMainThread private val handler: Handler,
shellInit: ShellInit,
@@ -161,6 +162,14 @@ class DesktopMixedTransitionHandler(
finishTransaction,
finishCallback
)
+ is PendingMixedTransition.Minimize -> animateMinimizeTransition(
+ pending,
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
}
}
@@ -272,6 +281,42 @@ class DesktopMixedTransitionHandler(
)
}
+ private fun animateMinimizeTransition(
+ pending: PendingMixedTransition.Minimize,
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback,
+ ): Boolean {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false
+
+ val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask)
+ if (minimizeChange == null) {
+ logW("Should have minimizing desktop task")
+ return false
+ }
+ if (pending.isLastTask) {
+ // Dispatch close desktop task animation to the default transition handlers.
+ return dispatchToLeftoverHandler(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback
+ )
+ }
+
+ // Animate minimizing desktop task transition with [DesktopBackNavigationTransitionHandler].
+ return desktopBackNavigationTransitionHandler.startAnimation(
+ transition,
+ info,
+ startTransaction,
+ finishTransaction,
+ finishCallback,
+ )
+ }
+
override fun onTransitionConsumed(
transition: IBinder,
aborted: Boolean,
@@ -400,6 +445,14 @@ class DesktopMixedTransitionHandler(
val minimizingTask: Int?,
val exitingImmersiveTask: Int?,
) : PendingMixedTransition()
+
+ /** A task is minimizing. This should be used for task going to back and some closing cases
+ * with back navigation. */
+ data class Minimize(
+ override val transition: IBinder,
+ val minimizingTask: Int,
+ val isLastTask: Boolean,
+ ) : PendingMixedTransition()
}
private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index bed484c7a532..39586e39fdd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -594,6 +594,10 @@ class DesktopModeEventLogger {
FrameworkStatsLog
.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER
),
+ DRAG_TO_TOP_RESIZE_TRIGGER(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_TO_TOP_RESIZE_TRIGGER
+ ),
}
/**
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 927fd88fb4ff..223038f84418 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
@@ -871,11 +871,10 @@ class DesktopTasksController(
return
}
- // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top.
desktopModeEventLogger.logTaskResizingStarted(
- ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
)
- toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent)
+ toggleDesktopTaskSize(taskInfo, ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent)
}
private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -1291,7 +1290,11 @@ class DesktopTasksController(
// Check if freeform task launch during recents should be handled
shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
- TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
+ TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
+ task,
+ transition,
+ request.type
+ )
// Check if the top task shouldn't be allowed to enter desktop mode
isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -1621,7 +1624,7 @@ class DesktopTasksController(
}
/** Handle task closing by removing wallpaper activity if it's the last active task */
- private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
+ private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? {
logV("handleTaskClosing")
if (!isDesktopModeShowing(task.displayId))
return null
@@ -1637,8 +1640,15 @@ class DesktopTasksController(
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
taskRepository.addClosingTask(task.displayId, task.taskId)
desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+ } else if (requestType == TRANSIT_CLOSE) {
+ // Handle closing tasks, tasks that are going to back are handled in
+ // [DesktopTasksTransitionObserver].
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1
+ )
+ )
}
-
taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
doesAnyTaskRequireTaskbarRounding(
task.displayId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index d1534da9a078..c39c715e685c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -46,6 +46,7 @@ class DesktopTasksTransitionObserver(
private val desktopRepository: DesktopRepository,
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
shellInit: ShellInit
) : Transitions.TransitionObserver {
@@ -71,7 +72,7 @@ class DesktopTasksTransitionObserver(
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
- handleBackNavigation(info)
+ handleBackNavigation(transition, info)
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
@@ -95,7 +96,7 @@ class DesktopTasksTransitionObserver(
}
}
- private fun handleBackNavigation(info: TransitionInfo) {
+ private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
// When default back navigation happens, transition type is TO_BACK and the change is
// TO_BACK. Mark the task going to back as minimized.
if (info.type == TRANSIT_TO_BACK) {
@@ -105,10 +106,14 @@ class DesktopTasksTransitionObserver(
continue
}
- if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
+ if (visibleTaskCount > 0 &&
change.mode == TRANSIT_TO_BACK &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ desktopMixedTransitionHandler.addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+ transition, taskInfo.taskId, visibleTaskCount == 1))
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4aeecbec7dfb..5276d9d6a4df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -27,6 +27,7 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -176,12 +177,12 @@ public class PipAnimationController {
public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, float startingAngle,
- @Surface.Rotation int rotationDelta) {
+ @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
endBounds, sourceHintRect, direction, 0 /* startingAngle */,
- rotationDelta));
+ rotationDelta, alwaysAnimateTaskBounds));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -197,7 +198,8 @@ public class PipAnimationController {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
- endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
+ endBounds, sourceHintRect, direction, startingAngle, rotationDelta,
+ alwaysAnimateTaskBounds));
}
return mCurrentAnimator;
}
@@ -585,28 +587,32 @@ public class PipAnimationController {
static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint,
@PipAnimationController.TransitionDirection int direction, float startingAngle,
- @Surface.Rotation int rotationDelta) {
+ @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
final boolean isOutPipDirection = isOutPipDirection(direction);
final boolean isInPipDirection = isInPipDirection(direction);
// Just for simplicity we'll interpolate between the source rect hint insets and empty
// insets to calculate the window crop
final Rect initialSourceValue;
final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
- final boolean hasNonMatchFrame = mainWindowFrame != null;
+ final AppCompatTaskInfo compatInfo = taskInfo.appCompatTaskInfo;
+ final boolean isSizeCompatOrLetterboxed = compatInfo.isTopActivityInSizeCompat()
+ || compatInfo.isTopActivityLetterboxed();
+ // For the animation to swipe PIP to home or restore a PIP task from home, we don't
+ // override to the main window frame since we should animate the whole task.
+ final boolean shouldUseMainWindowFrame = mainWindowFrame != null
+ && !alwaysAnimateTaskBounds && !isSizeCompatOrLetterboxed;
final boolean changeOrientation =
rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
final Rect baseBounds = new Rect(baseValue);
final Rect startBounds = new Rect(startValue);
final Rect endBounds = new Rect(endValue);
if (isOutPipDirection) {
- // TODO(b/356277166): handle rotation change with activity that provides main window
- // frame.
- if (hasNonMatchFrame && !changeOrientation) {
+ if (shouldUseMainWindowFrame && !changeOrientation) {
endBounds.set(mainWindowFrame);
}
initialSourceValue = new Rect(endBounds);
} else if (isInPipDirection) {
- if (hasNonMatchFrame) {
+ if (shouldUseMainWindowFrame) {
baseBounds.set(mainWindowFrame);
if (startValue.equals(baseValue)) {
// If the start value is at initial state as in PIP animation, also override
@@ -635,9 +641,19 @@ public class PipAnimationController {
if (changeOrientation) {
lastEndRect = new Rect(endBounds);
rotatedEndRect = new Rect(endBounds);
- // Rotate the end bounds according to the rotation delta because the display will
- // be rotated to the same orientation.
- rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+ // TODO(b/375977163): polish the animation to restoring the PIP task back from
+ // swipe-pip-to-home. Ideally we should send the transitionInfo after reparenting
+ // the PIP activity back to the original task.
+ if (shouldUseMainWindowFrame) {
+ // If we should animate the main window frame, set it to the rotatedRect
+ // instead. The end bounds reported by transitionInfo is the bounds before
+ // rotation, while main window frame is calculated after the rotation.
+ rotatedEndRect.set(mainWindowFrame);
+ } else {
+ // Rotate the end bounds according to the rotation delta because the display
+ // will be rotated to the same orientation.
+ rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+ }
// Use the rect that has the same orientation as the hint rect.
initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
} else {
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 86c826a680f6..30f1948efa2d 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
@@ -1880,9 +1880,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
? mPipBoundsState.getBounds() : currentBounds;
final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
&& mPipAnimationController.getCurrentAnimator().isRunning();
+ // For resize animation, we always animate the whole PIP task bounds.
final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
- sourceHintRect, direction, startingAngle, rotationDelta);
+ sourceHintRect, direction, startingAngle, rotationDelta,
+ true /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(direction)
.setPipTransactionHandler(mPipTransactionHandler)
.setDuration(durationMs);
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 8220ea5ea575..f7aed4401247 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
@@ -891,7 +891,8 @@ public class PipTransition extends PipTransitionController {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
- 0 /* startingAngle */, pipRotateDelta);
+ 0 /* startingAngle */, pipRotateDelta,
+ false /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
@@ -906,7 +907,7 @@ public class PipTransition extends PipTransitionController {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
- 0 /* startingAngle */, rotationDelta);
+ 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
.setDuration(mEnterExitAnimationDuration);
if (startTransaction != null) {
@@ -1102,8 +1103,6 @@ public class PipTransition extends PipTransitionController {
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
- // TODO(b/356277166): add support to swipe PIP to home with
- // non-match parent activity.
handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
sourceHintRect, destinationBounds, taskInfo);
return;
@@ -1125,7 +1124,7 @@ public class PipTransition extends PipTransitionController {
if (enterAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, rotationDelta);
+ 0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation. We also temporarily disallow app icon overlay and use color overlay
@@ -1248,10 +1247,14 @@ public class PipTransition extends PipTransitionController {
// to avoid flicker.
final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
pipTaskInfo.displayCutoutInsets.setEmpty();
+ // Always use the task bounds even if the PIP activity doesn't match parent because the app
+ // and the whole task will move behind. We should animate the whole task bounds in this
+ // case.
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */,
+ true /* alwaysAnimateTaskBounds */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
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 19a73f3631f2..cc0e1df115c2 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,6 +55,7 @@ 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;
@@ -1098,11 +1099,16 @@ 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) {
+ if (mSideStage.mVisible && updateBounds) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
onLayoutSizeChanged(mSplitLayout);
@@ -1193,7 +1199,6 @@ 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);
}
@@ -1593,13 +1598,6 @@ 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/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 29e4b5bca5cc..9fcf98b9efc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -353,7 +353,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
boolean isSeamlessDisplayChange = false;
if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
- if (info.getType() == TRANSIT_CHANGE) {
+ if (info.getType() == TRANSIT_CHANGE || isOnlyTranslucent) {
final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
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/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 7265fb8f8027..c9f2d2e8c0e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ContentResolver;
import android.content.Context;
@@ -110,6 +111,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
}
mMainExecutor.execute(() -> {
mExclusionRegion.set(systemGestureExclusion);
+ onExclusionRegionChanged(displayId, mExclusionRegion);
});
}
};
@@ -163,7 +165,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
boolean isFocusedGlobally) {
final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null) {
- decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion);
}
}
@@ -199,9 +201,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
if (enableDisplayFocusInShellTransitions()) {
// Pass the current global focus status to avoid updates outside of a ShellTransition.
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
} else {
- decoration.relayout(taskInfo, taskInfo.isFocused);
+ decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
}
}
@@ -240,7 +242,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
}
@@ -254,7 +256,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
@Override
@@ -266,6 +268,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
decoration.close();
}
+ private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) {
+ final int decorCount = mWindowDecorByTaskId.size();
+ for (int i = 0; i < decorCount; i++) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration.mTaskInfo.displayId != displayId) continue;
+ decoration.onExclusionRegionChanged(exclusionRegion);
+ }
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return true;
@@ -333,7 +344,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT
windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
}
private class CaptionTouchEventListener implements
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c9546731a193..982fda0ddf36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -36,6 +36,7 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.util.Size;
@@ -174,7 +175,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
@Override
- void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
+ void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -186,7 +188,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
}
@VisibleForTesting
@@ -198,7 +200,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
boolean isStatusBarVisible,
boolean isKeyguardVisibleAndOccluded,
InsetsState displayInsetsState,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region globalExclusionRegion) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
@@ -210,6 +213,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
|| (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
+ relayoutParams.mDisplayExclusionRegion.set(globalExclusionRegion);
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
// If the app is requesting to customize the caption bar, allow input to fall
@@ -236,7 +240,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region globalExclusionRegion) {
final boolean isFreeform =
taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue()
@@ -249,7 +254,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ globalExclusionRegion);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
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..d71e61a4c4de 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
@@ -220,6 +220,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
mMainExecutor.execute(() -> {
mExclusionRegion.set(systemGestureExclusion);
+ onExclusionRegionChanged(displayId, mExclusionRegion);
});
}
};
@@ -432,7 +433,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
boolean isFocusedGlobally) {
final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null) {
- decor.relayout(decor.mTaskInfo, isFocusedGlobally);
+ decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion);
}
}
@@ -465,15 +466,15 @@ 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);
}
if (enableDisplayFocusInShellTransitions()) {
// Pass the current global focus status to avoid updates outside of a ShellTransition.
- decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
+ decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
} else {
- decoration.relayout(taskInfo, taskInfo.isFocused);
+ decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
}
mActivityOrientationChangeHandler.ifPresent(handler ->
handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
@@ -514,7 +515,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
} else {
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
}
}
@@ -528,7 +530,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
}
@Override
@@ -539,7 +542,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
@@ -548,6 +551,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
+ private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) {
+ final int decorCount = mWindowDecorByTaskId.size();
+ for (int i = 0; i < decorCount; i++) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration.mTaskInfo.displayId != displayId) continue;
+ decoration.onExclusionRegionChanged(exclusionRegion);
+ }
+ }
+
private void openHandleMenu(int taskId) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo)
@@ -750,10 +762,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
/**
* Whether to pilfer the next motion event to send cancellations to the windows below.
- * Useful when the caption window is spy and the gesture should be handle by the system
+ * Useful when the caption window is spy and the gesture should be handled by the system
* instead of by the app for their custom header content.
+ * Should not have any effect when {@link Flags#enableAccessibleCustomHeaders()}, because
+ * a spy window is not used then.
*/
- private boolean mShouldPilferCaptionEvents;
+ private boolean mIsCustomHeaderGesture;
+ private boolean mIsResizeGesture;
private boolean mIsDragging;
private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
@@ -767,7 +782,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mTaskToken = taskInfo.token;
mDragPositioningCallback = dragPositioningCallback;
final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- final long appHandleHoldToDragDuration = Flags.enableHoldToDragAppHandle()
+ final long appHandleHoldToDragDuration =
+ DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue()
? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0;
mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration,
touchSlop);
@@ -867,7 +883,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// to offset position relative to caption as a whole.
int[] viewLocation = new int[2];
v.getLocationInWindow(viewLocation);
- final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e,
+ mIsResizeGesture = decoration.shouldResizeListenerHandleEvent(e,
new Point(viewLocation[0], viewLocation[1]));
// The caption window may be a spy window when the caption background is
// transparent, which means events will fall through to the app window. Make
@@ -875,21 +891,23 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// customizable region and what the app reported as exclusion areas, because
// the drag-move or other caption gestures should take priority outside those
// regions.
- mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
- && downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
+ mIsCustomHeaderGesture = downInCustomizableCaptionRegion
+ && downInExclusionRegion && isTransparentCaption;
}
- if (!mShouldPilferCaptionEvents) {
- // The event will be handled by a window below or pilfered by resize handler.
+ if (mIsCustomHeaderGesture || mIsResizeGesture) {
+ // The event will be handled by the custom window below or pilfered by resize
+ // handler.
return false;
}
- // Otherwise pilfer so that windows below receive cancellations for this gesture, and
- // continue normal handling as a caption gesture.
- if (mInputManager != null) {
+ if (mInputManager != null
+ && !Flags.enableAccessibleCustomHeaders()) {
+ // Pilfer so that windows below receive cancellations for this gesture.
mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
}
if (isUpOrCancel) {
// Gesture is finished, reset state.
- mShouldPilferCaptionEvents = false;
+ mIsCustomHeaderGesture = false;
+ mIsResizeGesture = false;
}
if (isAppHandle) {
return mHandleDragDetector.onMotionEvent(v, e);
@@ -1234,7 +1252,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 +1295,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;
}
@@ -1592,8 +1610,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
- mFocusTransitionObserver.hasGlobalFocus(taskInfo));
- if (!Flags.enableHandleInputFix()) {
+ mFocusTransitionObserver.hasGlobalFocus(taskInfo),
+ mExclusionRegion);
+ if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
incrementEventReceiverTasks(taskInfo.displayId);
}
}
@@ -1618,6 +1637,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
+ pw.println(innerPrefix + "mExclusionRegion=" + mExclusionRegion);
}
private class DesktopModeOnTaskRepositionAnimationListener
@@ -1754,7 +1774,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..cdcf14e0cbf3 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
@@ -394,7 +394,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
@Override
- void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// The visibility, crop and position of the task should only be set when a task is
// fluid resizing. In all other cases, it is expected that the transition handler sets
@@ -415,7 +416,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
// causes flickering. See b/270202228.
final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus);
+ hasGlobalFocus, displayExclusionRegion);
if (!applyTransactionOnDraw) {
t.apply();
}
@@ -442,18 +443,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> show its header in sync since it's an integral part
// of the window itself - a delayed header might cause bad UX.
relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
} else {
// The Task is outside Freeform mode -> allow the handle view to be delayed since the
// handle is just a small addition to the window.
relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
}
Trace.endSection();
}
@@ -462,11 +463,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
// Clear the current ViewHost runnable as we will update the ViewHost here
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
- shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
+ shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion);
if (mResult.mRootView != null) {
updateViewHost(mRelayoutParams, startT, mResult);
}
@@ -489,7 +490,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
if (applyStartTransactionOnDraw) {
throw new IllegalArgumentException(
"We cannot both sync viewhost ondraw and delay viewhost creation.");
@@ -498,7 +500,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
clearCurrentViewHostRunnable();
updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
- hasGlobalFocus);
+ hasGlobalFocus, displayExclusionRegion);
if (mResult.mRootView == null) {
// This means something blocks the window decor from showing, e.g. the task is hidden.
// Nothing is set up in this case including the decoration surface.
@@ -513,7 +515,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
if (Flags.enableDesktopWindowingAppToWeb()) {
setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
@@ -538,7 +540,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
mIsKeyguardVisibleAndOccluded, inFullImmersive,
- mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
+ mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
+ displayExclusionRegion);
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -628,13 +631,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Nullable
private Intent getBrowserLink() {
- // Do not show browser link in browser applications
- final ComponentName baseActivity = mTaskInfo.baseActivity;
- if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
- baseActivity.getPackageName(), mUserContext.getUserId())) {
- return null;
- }
-
final Uri browserLink;
// If the captured link is available and has not expired, return the captured link.
// Otherwise, return the generic link which is set to null if a generic link is unavailable.
@@ -651,6 +647,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
+ @Nullable
+ private Intent getAppLink() {
+ return mWebUri == null ? null
+ : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager());
+ }
+
+ private boolean isBrowserApp() {
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ return baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
+ baseActivity.getPackageName(), mUserContext.getUserId());
+ }
+
UserHandle getUser() {
return mUserContext.getUser();
}
@@ -807,7 +815,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void disposeStatusBarInputLayer() {
if (!isAppHandle(mWindowDecorViewHolder)
- || !Flags.enableHandleInputFix()) {
+ || !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
return;
}
asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
@@ -874,7 +882,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean isKeyguardVisibleAndOccluded,
boolean inFullImmersiveMode,
@NonNull InsetsState displayInsetsState,
- boolean hasGlobalFocus) {
+ boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
final boolean isAppHeader =
captionLayoutId == R.layout.desktop_mode_app_header;
@@ -885,6 +894,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
relayoutParams.mHasGlobalFocus = hasGlobalFocus;
+ relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
final boolean showCaption;
if (Flags.enableFullyImmersiveInDesktop()) {
@@ -910,10 +920,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode;
if (isAppHeader) {
if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
- // If the app is requesting to customize the caption bar, allow input to fall
- // through to the windows below so that the app can respond to input events on
- // their custom content.
- relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ // The app is requesting to customize the caption bar, which means input on
+ // customizable/exclusion regions must go to the app instead of to the system.
+ // This may be accomplished with spy windows or custom touchable regions:
+ if (Flags.enableAccessibleCustomHeaders()) {
+ // Set the touchable region of the caption to only the areas where input should
+ // be handled by the system (i.e. non custom-excluded areas). The region will
+ // be calculated based on occluding caption elements and exclusion areas
+ // reported by the app.
+ relayoutParams.mLimitTouchRegionToSystemAreas = true;
+ } else {
+ // Allow input to fall through to the windows below so that the app can respond
+ // to input events on their custom content.
+ relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ }
} else {
if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) {
// Force-consume the caption bar insets when the app tries to hide the caption.
@@ -951,7 +971,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
@@ -1368,6 +1388,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
.shouldShowChangeAspectRatioButton(mTaskInfo);
final boolean inDesktopImmersive = mDesktopRepository
.isTaskInFullImmersiveState(mTaskInfo.taskId);
+ final boolean isBrowserApp = isBrowserApp();
mHandleMenu = mHandleMenuFactory.create(
this,
mWindowManagerWrapper,
@@ -1379,7 +1400,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
supportsMultiInstance,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- getBrowserLink(),
+ isBrowserApp,
+ isBrowserApp ? getAppLink() : getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX,
@@ -1560,13 +1582,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 +1625,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 +1638,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 +1652,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..54c247bff984 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,12 +39,14 @@ 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.StringRes
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.apptoweb.isBrowserApp
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -73,7 +75,8 @@ class HandleMenu(
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
private val shouldShowChangeAspectRatioButton: Boolean,
- private val openInBrowserIntent: Intent?,
+ private val isBrowserApp: Boolean,
+ private val openInAppOrBrowserIntent: Intent?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int,
@@ -83,7 +86,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)
@@ -111,7 +114,7 @@ class HandleMenu(
private val globalMenuPosition: Point = Point()
private val shouldShowBrowserPill: Boolean
- get() = openInBrowserIntent != null
+ get() = openInAppOrBrowserIntent != null
private val shouldShowMoreActionsPill: Boolean
get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
@@ -128,7 +131,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
- openInBrowserClickListener: (Intent) -> Unit,
+ openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
@@ -146,7 +149,7 @@ class HandleMenu(
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
- openInBrowserClickListener = openInBrowserClickListener,
+ openInAppOrBrowserClickListener = openInAppOrBrowserClickListener,
onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
onOutsideTouchListener = onOutsideTouchListener,
@@ -167,7 +170,7 @@ class HandleMenu(
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
onChangeAspectRatioClickListener: () -> Unit,
- openInBrowserClickListener: (Intent) -> Unit,
+ openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
@@ -181,7 +184,8 @@ class HandleMenu(
shouldShowBrowserPill = shouldShowBrowserPill,
shouldShowNewWindowButton = shouldShowNewWindowButton,
shouldShowManageWindowsButton = shouldShowManageWindowsButton,
- shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
+ shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton,
+ isBrowserApp = isBrowserApp
).apply {
bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
this.onToDesktopClickListener = onToDesktopClickListener
@@ -190,8 +194,8 @@ class HandleMenu(
this.onNewWindowClickListener = onNewWindowClickListener
this.onManageWindowsClickListener = onManageWindowsClickListener
this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
- this.onOpenInBrowserClickListener = {
- openInBrowserClickListener.invoke(openInBrowserIntent!!)
+ this.onOpenInAppOrBrowserClickListener = {
+ openInAppOrBrowserClickListener.invoke(openInAppOrBrowserIntent!!)
}
this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
this.onCloseMenuClickListener = onCloseMenuClickListener
@@ -201,7 +205,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 +242,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.
@@ -435,14 +440,15 @@ class HandleMenu(
/** The view within the Handle Menu, with options to change the windowing mode and more. */
@SuppressLint("ClickableViewAccessibility")
class HandleMenuView(
- context: Context,
+ private val context: Context,
menuWidth: Int,
captionHeight: Int,
private val shouldShowWindowingPill: Boolean,
private val shouldShowBrowserPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
- private val shouldShowChangeAspectRatioButton: Boolean
+ private val shouldShowChangeAspectRatioButton: Boolean,
+ private val isBrowserApp: Boolean
) {
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -472,11 +478,12 @@ class HandleMenu(
private val changeAspectRatioBtn = moreActionsPill
.requireViewById<Button>(R.id.change_aspect_ratio_button)
- // Open in Browser Pill.
- private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
- private val browserBtn = openInBrowserPill.requireViewById<Button>(
- R.id.open_in_browser_button)
- private val openByDefaultBtn = openInBrowserPill.requireViewById<ImageButton>(
+ // Open in Browser/App Pill.
+ private val openInAppOrBrowserPill = rootView.requireViewById<View>(
+ R.id.open_in_app_or_browser_pill)
+ private val openInAppOrBrowserBtn = openInAppOrBrowserPill.requireViewById<Button>(
+ R.id.open_in_app_or_browser_button)
+ private val openByDefaultBtn = openInAppOrBrowserPill.requireViewById<ImageButton>(
R.id.open_by_default_button)
private val decorThemeUtil = DecorThemeUtil(context)
private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
@@ -490,7 +497,7 @@ class HandleMenu(
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
var onChangeAspectRatioClickListener: (() -> Unit)? = null
- var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onOpenInAppOrBrowserClickListener: (() -> Unit)? = null
var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
var onOutsideTouchListener: (() -> Unit)? = null
@@ -499,7 +506,7 @@ class HandleMenu(
fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() }
splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
- browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ openInAppOrBrowserBtn.setOnClickListener { onOpenInAppOrBrowserClickListener?.invoke() }
openByDefaultBtn.setOnClickListener {
onOpenByDefaultClickListener?.invoke()
}
@@ -535,10 +542,10 @@ class HandleMenu(
if (shouldShowMoreActionsPill) {
bindMoreActionsPill(style)
}
- bindOpenInBrowserPill(style)
+ bindOpenInAppOrBrowserPill(style)
}
- /** Animates the menu opening. */
+ /** Animates the menu openInAppOrBrowserg. */
fun animateOpenMenu() {
if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
animator.animateCaptionHandleExpandToOpen()
@@ -660,13 +667,20 @@ class HandleMenu(
}
}
- private fun bindOpenInBrowserPill(style: MenuStyle) {
- openInBrowserPill.apply {
+ private fun bindOpenInAppOrBrowserPill(style: MenuStyle) {
+ openInAppOrBrowserPill.apply {
isGone = !shouldShowBrowserPill
background.setTint(style.backgroundColor)
}
- browserBtn.apply {
+ val btnText = if (isBrowserApp) {
+ getString(R.string.open_in_app_text)
+ } else {
+ getString(R.string.open_in_browser_text)
+ }
+ openInAppOrBrowserBtn.apply {
+ text = btnText
+ contentDescription = btnText
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
@@ -674,6 +688,8 @@ class HandleMenu(
openByDefaultBtn.imageTintList = ColorStateList.valueOf(style.textColor)
}
+ private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+
private data class MenuStyle(
@ColorInt val backgroundColor: Int,
@ColorInt val textColor: Int,
@@ -708,7 +724,8 @@ interface HandleMenuFactory {
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
- openInBrowserIntent: Intent?,
+ isBrowserApp: Boolean,
+ openInAppOrBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -729,7 +746,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
- openInBrowserIntent: Intent?,
+ isBrowserApp: Boolean,
+ openInAppOrBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -746,7 +764,8 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowNewWindowButton,
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
- openInBrowserIntent,
+ isBrowserApp,
+ openInAppOrBrowserIntent,
captionWidth,
captionHeight,
captionX,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 0c475f12f53b..470e5a1d88b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -74,7 +74,8 @@ class HandleMenuAnimator(
private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill)
private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill)
private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill)
- private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill)
+ private val openInAppOrBrowserPill: ViewGroup =
+ handleMenu.requireViewById(R.id.open_in_app_or_browser_pill)
/** Animates the opening of the handle menu. */
fun animateOpen() {
@@ -83,7 +84,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
- animateOpenInBrowserPill()
+ animateOpenInAppOrBrowserPill()
runAnimations {
appInfoPill.post {
appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
@@ -103,7 +104,7 @@ class HandleMenuAnimator(
animateAppInfoPillOpen()
animateWindowingPillOpen()
animateMoreActionsPillOpen()
- animateOpenInBrowserPill()
+ animateOpenInAppOrBrowserPill()
runAnimations {
appInfoPill.post {
appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
@@ -124,7 +125,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
- openInBrowserPillClose()
+ openInAppOrBrowserPillClose()
runAnimations(after)
}
@@ -141,7 +142,7 @@ class HandleMenuAnimator(
animateAppInfoPillFadeOut()
windowingPillClose()
moreActionsPillClose()
- openInBrowserPillClose()
+ openInAppOrBrowserPillClose()
runAnimations(after)
}
@@ -154,7 +155,7 @@ class HandleMenuAnimator(
appInfoPill.children.forEach { it.alpha = 0f }
windowingPill.alpha = 0f
moreActionsPill.alpha = 0f
- openInBrowserPill.alpha = 0f
+ openInAppOrBrowserPill.alpha = 0f
// Setup pivots.
handleMenu.pivotX = menuWidth / 2f
@@ -166,8 +167,8 @@ class HandleMenuAnimator(
moreActionsPill.pivotX = menuWidth / 2f
moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat()
- openInBrowserPill.pivotX = menuWidth / 2f
- openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
+ openInAppOrBrowserPill.pivotX = menuWidth / 2f
+ openInAppOrBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat()
}
private fun animateAppInfoPillOpen() {
@@ -297,36 +298,36 @@ class HandleMenuAnimator(
}
}
- private fun animateOpenInBrowserPill() {
+ private fun animateOpenInAppOrBrowserPill() {
// Open in Browser X & Y Scaling Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_OPEN_DELAY
duration = BODY_SCALE_OPEN_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply {
startDelay = BODY_SCALE_OPEN_DELAY
duration = BODY_SCALE_OPEN_DURATION
}
// Open in Browser Opacity Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
duration = BODY_ALPHA_OPEN_DURATION
}
// Open in Browser Elevation Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Z, 1f).apply {
startDelay = ELEVATION_OPEN_DELAY
duration = BODY_ELEVATION_OPEN_DURATION
}
// Open in Browser Button Opacity Animation
- val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button)
+ val button = openInAppOrBrowserPill.requireViewById<Button>(R.id.open_in_app_or_browser_button)
animators +=
ObjectAnimator.ofFloat(button, ALPHA, 1f).apply {
startDelay = BODY_ALPHA_OPEN_DELAY
@@ -438,33 +439,33 @@ class HandleMenuAnimator(
}
}
- private fun openInBrowserPillClose() {
+ private fun openInAppOrBrowserPillClose() {
// Open in Browser X & Y Scaling Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply {
duration = BODY_CLOSE_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply {
duration = BODY_CLOSE_DURATION
}
// Open in Browser Opacity Animation
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply {
duration = BODY_CLOSE_DURATION
}
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, ALPHA, 0f).apply {
duration = BODY_CLOSE_DURATION
}
// Upward Open in Browser y-translation Animation
val yStart: Float = -captionHeight / 2
animators +=
- ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply {
+ ObjectAnimator.ofFloat(openInAppOrBrowserPill, TRANSLATION_Y, yStart).apply {
duration = BODY_CLOSE_DURATION
}
}
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/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index b016c755e323..a3c75bf33cde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -127,7 +127,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
mDisplayController.removeDisplayWindowListener(this);
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
};
@@ -143,7 +143,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
SurfaceControl mDecorationContainerSurface;
SurfaceControl mCaptionContainerSurface;
- private WindowlessWindowManager mCaptionWindowManager;
+ private CaptionWindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
@@ -152,6 +152,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
boolean mIsStatusBarVisible;
boolean mIsKeyguardVisibleAndOccluded;
boolean mHasGlobalFocus;
+ final Region mExclusionRegion = Region.obtain();
/** The most recent set of insets applied to this window decoration. */
private WindowDecorationInsets mWindowDecorationInsets;
@@ -218,7 +219,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
* constructor.
* @param hasGlobalFocus Whether the task is focused
*/
- abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus);
+ abstract void relayout(RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion);
/**
* Used by the {@link DragPositioningCallback} associated with the implementing class to
@@ -244,6 +246,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTaskInfo = params.mRunningTaskInfo;
}
mHasGlobalFocus = params.mHasGlobalFocus;
+ mExclusionRegion.set(params.mDisplayExclusionRegion);
final int oldLayoutResId = mLayoutResId;
mLayoutResId = params.mLayoutResId;
@@ -402,7 +405,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int elementWidthPx =
resources.getDimensionPixelSize(element.mWidthResId);
boundingRects[i] =
- calculateBoundingRect(element, elementWidthPx, captionInsetsRect);
+ calculateBoundingRectLocal(element, elementWidthPx, captionInsetsRect);
// Subtract the regions used by the caption elements, the rest is
// customizable.
if (params.hasInputFeatureSpy()) {
@@ -477,9 +480,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
if (mCaptionWindowManager == null) {
// Put caption under a container surface because ViewRootImpl sets the destination frame
// of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new WindowlessWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface,
- null /* hostInputToken */);
+ mCaptionWindowManager = new CaptionWindowlessWindowManager(
+ mTaskInfo.getConfiguration(), mCaptionContainerSurface);
}
mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
final WindowManager.LayoutParams lp =
@@ -492,6 +494,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
lp.inputFeatures = params.mInputFeatures;
+ final Rect localCaptionBounds = new Rect(
+ outResult.mCaptionX,
+ outResult.mCaptionY,
+ outResult.mCaptionX + outResult.mCaptionWidth,
+ outResult.mCaptionY + outResult.mCaptionHeight);
+ final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
+ ? calculateLimitedTouchableRegion(params, localCaptionBounds)
+ : null;
if (mViewHost == null) {
Trace.beginSection("CaptionViewHostLayout-new");
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
@@ -503,6 +513,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+ if (params.mLimitTouchRegionToSystemAreas) {
+ mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
+ }
mViewHost.setView(outResult.mRootView, lp);
Trace.endSection();
} else {
@@ -514,13 +527,71 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
+ if (params.mLimitTouchRegionToSystemAreas) {
+ mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
+ }
mViewHost.relayout(lp);
Trace.endSection();
}
+ if (touchableRegion != null) {
+ touchableRegion.recycle();
+ }
Trace.endSection(); // CaptionViewHostLayout
}
- private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
+ @NonNull
+ private Region calculateLimitedTouchableRegion(
+ RelayoutParams params,
+ @NonNull Rect localCaptionBounds) {
+ // Make caption bounds relative to display to align with exclusion region.
+ final Point positionInParent = params.mRunningTaskInfo.positionInParent;
+ final Rect captionBoundsInDisplay = new Rect(localCaptionBounds);
+ captionBoundsInDisplay.offsetTo(positionInParent.x, positionInParent.y);
+
+ final Region boundingRects = calculateBoundingRectsRegion(params, captionBoundsInDisplay);
+
+ final Region customizedRegion = Region.obtain();
+ customizedRegion.set(captionBoundsInDisplay);
+ customizedRegion.op(boundingRects, Region.Op.DIFFERENCE);
+ customizedRegion.op(params.mDisplayExclusionRegion, Region.Op.INTERSECT);
+
+ final Region touchableRegion = Region.obtain();
+ touchableRegion.set(captionBoundsInDisplay);
+ touchableRegion.op(customizedRegion, Region.Op.DIFFERENCE);
+ // Return resulting region back to window coordinates.
+ touchableRegion.translate(-positionInParent.x, -positionInParent.y);
+
+ boundingRects.recycle();
+ customizedRegion.recycle();
+ return touchableRegion;
+ }
+
+ @NonNull
+ private Region calculateBoundingRectsRegion(
+ @NonNull RelayoutParams params,
+ @NonNull Rect captionBoundsInDisplay) {
+ final int numOfElements = params.mOccludingCaptionElements.size();
+ final Region region = Region.obtain();
+ if (numOfElements == 0) {
+ // The entire caption is a bounding rect.
+ region.set(captionBoundsInDisplay);
+ return region;
+ }
+ final Resources resources = mDecorWindowContext.getResources();
+ for (int i = 0; i < numOfElements; i++) {
+ final OccludingCaptionElement element = params.mOccludingCaptionElements.get(i);
+ final int elementWidthPx = resources.getDimensionPixelSize(element.mWidthResId);
+ final Rect boundingRect = calculateBoundingRectLocal(element, elementWidthPx,
+ captionBoundsInDisplay);
+ // Bounding rect is initially calculated relative to the caption, so offset it to make
+ // it relative to the display.
+ boundingRect.offset(captionBoundsInDisplay.left, captionBoundsInDisplay.top);
+ region.union(boundingRect);
+ }
+ return region;
+ }
+
+ private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element,
int elementWidthPx, @NonNull Rect captionRect) {
switch (element.mAlignment) {
case START -> {
@@ -539,7 +610,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mIsKeyguardVisibleAndOccluded = visible && occluded;
final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
if (changed) {
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
}
@@ -549,10 +620,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
if (changed) {
- relayout(mTaskInfo, mHasGlobalFocus);
+ relayout(mTaskInfo, mHasGlobalFocus, mExclusionRegion);
}
}
+ void onExclusionRegionChanged(@NonNull Region exclusionRegion) {
+ relayout(mTaskInfo, mHasGlobalFocus, exclusionRegion);
+ }
+
/**
* Update caption visibility state and views.
*/
@@ -751,9 +826,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mCaptionHeightId;
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
+ boolean mLimitTouchRegionToSystemAreas;
int mInputFeatures;
boolean mIsInsetSource = true;
@InsetsSource.Flags int mInsetSourceFlags;
+ final Region mDisplayExclusionRegion = Region.obtain();
int mShadowRadiusId;
int mCornerRadius;
@@ -772,9 +849,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
+ mLimitTouchRegionToSystemAreas = false;
mInputFeatures = 0;
mIsInsetSource = true;
mInsetSourceFlags = 0;
+ mDisplayExclusionRegion.setEmpty();
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
@@ -830,6 +909,19 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
+ private static class CaptionWindowlessWindowManager extends WindowlessWindowManager {
+ CaptionWindowlessWindowManager(
+ @NonNull Configuration configuration,
+ @NonNull SurfaceControl rootSurface) {
+ super(configuration, rootSurface, /* hostInputToken= */ null);
+ }
+
+ /** Set the view host's touchable region. */
+ void setTouchRegion(@NonNull SurfaceControlViewHost viewHost, @NonNull Region region) {
+ setTouchRegion(viewHost.getWindowToken().asBinder(), region);
+ }
+ }
+
@VisibleForTesting
public interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
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/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4fe66f3357a3..4cddf31321d6 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -23,8 +23,9 @@ import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
-import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
@@ -44,6 +45,7 @@ import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTop
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.common.Components.LAUNCHER
import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
@@ -365,5 +367,57 @@ class DesktopModeFlickerScenarios {
AppWindowAlignsWithOnlyOneDisplayCornerAtEnd(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+
+ val MINIMIZE_APP =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MINIMIZE_APP"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions
+ .filter { it.type == TransitionType.MINIMIZE }
+ .sortedByDescending { it.id }
+ .drop(1)
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
+
+ val MINIMIZE_LAST_APP =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("MINIMIZE_LAST_APP"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ val lastTransition =
+ transitions
+ .filter { it.type == TransitionType.MINIMIZE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(LAUNCHER),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
new file mode 100644
index 000000000000..58582b02c212
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsLandscape : MinimizeAppWindows(rotation = ROTATION_90) {
+ @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+ @Test
+ override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(MINIMIZE_APP)
+ .use(MINIMIZE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
new file mode 100644
index 000000000000..7970426a6ee8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsPortrait : MinimizeAppWindows() {
+ @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+ @Test
+ override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(MINIMIZE_APP)
+ .use(MINIMIZE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 824c4482c1e6..f442fdb31592 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -18,6 +18,7 @@ package com.android.wm.shell.scenarios
import android.tools.NavBar
import android.tools.Rotation
+import com.android.internal.R
import com.android.window.flags.Flags
import com.android.wm.shell.Utils
import org.junit.After
@@ -40,6 +41,9 @@ constructor(
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ // Skip the test when the drag-to-maximize is enabled on this device.
+ Assume.assumeFalse(Flags.enableDragToMaximize() &&
+ instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
testApp.enterDesktopWithDrag(wmHelper, device)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
new file mode 100644
index 000000000000..6df8d6fd7717
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WindowingMode
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val closingTaskLeash = mock<SurfaceControl>()
+ private val displayController = mock<DisplayController>()
+
+ private lateinit var handler: DesktopBackNavigationTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler =
+ DesktopBackNavigationTransitionHandler(
+ testExecutor,
+ testExecutor,
+ displayController
+ )
+ whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(handler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_openTransition_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate open transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionFullscreenTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate fullscreen task to back transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionOpeningFreeformTask_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ changeMode = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertFalse("Should not animate opening freeform task to back transition", animates)
+ }
+
+ @Test
+ fun startAnimation_toBackTransitionToBackFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should animate going to back freeform task close transition", animates)
+ }
+
+ @Test
+ fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info = createTransitionInfo(
+ type = TRANSIT_CLOSE,
+ changeMode = TRANSIT_CLOSE,
+ task = createTask(WINDOWING_MODE_FREEFORM)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should animate going to back freeform task close transition", animates)
+ }
+ private fun createTransitionInfo(
+ type: Int = WindowManager.TRANSIT_TO_BACK,
+ changeMode: Int = WindowManager.TRANSIT_TO_BACK,
+ task: RunningTaskInfo
+ ): TransitionInfo =
+ TransitionInfo(type, 0 /* flags */).apply {
+ addChange(
+ TransitionInfo.Change(mock(), closingTaskLeash).apply {
+ mode = changeMode
+ parent = null
+ taskInfo = task
+ }
+ )
+ }
+
+ private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(windowingMode)
+ .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index b06c2dad4ffc..f21f26443748 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -32,6 +32,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TransitionType
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -77,16 +78,28 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
- @Mock lateinit var transitions: Transitions
- @Mock lateinit var desktopRepository: DesktopRepository
- @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
- @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
- @Mock lateinit var desktopImmersiveController: DesktopImmersiveController
- @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock lateinit var mockHandler: Handler
- @Mock lateinit var closingTaskLeash: SurfaceControl
- @Mock lateinit var shellInit: ShellInit
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock
+ lateinit var transitions: Transitions
+ @Mock
+ lateinit var desktopRepository: DesktopRepository
+ @Mock
+ lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
+ @Mock
+ lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
+ @Mock
+ lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler
+ @Mock
+ lateinit var desktopImmersiveController: DesktopImmersiveController
+ @Mock
+ lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock
+ lateinit var mockHandler: Handler
+ @Mock
+ lateinit var closingTaskLeash: SurfaceControl
+ @Mock
+ lateinit var shellInit: ShellInit
+ @Mock
+ lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
private lateinit var mixedHandler: DesktopMixedTransitionHandler
@@ -100,6 +113,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
freeformTaskTransitionHandler,
closeDesktopTaskTransitionHandler,
desktopImmersiveController,
+ desktopBackNavigationTransitionHandler,
interactionJankMonitor,
mockHandler,
shellInit,
@@ -595,6 +609,87 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun startAnimation_withMinimizingDesktopTask_callsBackNavigationHandler() {
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+ whenever(
+ desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+ )
+ .thenReturn(true)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Minimize(
+ transition = transition,
+ minimizingTask = minimizingTask.taskId,
+ isLastTask = false,
+ )
+ )
+
+ val minimizingTaskChange = createChange(minimizingTask)
+ val started = mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ TRANSIT_TO_BACK,
+ listOf(minimizingTaskChange)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ assertTrue("Should delegate animation to back navigation transition handler", started)
+ verify(desktopBackNavigationTransitionHandler)
+ .startAnimation(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun startAnimation_withMinimizingLastDesktopTask_dispatchesTransition() {
+ val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val transition = Binder()
+ whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+ whenever(
+ desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+ )
+ .thenReturn(true)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Minimize(
+ transition = transition,
+ minimizingTask = minimizingTask.taskId,
+ isLastTask = true,
+ )
+ )
+
+ val minimizingTaskChange = createChange(minimizingTask)
+ mixedHandler.startAnimation(
+ transition = transition,
+ info =
+ createTransitionInfo(
+ TRANSIT_TO_BACK,
+ listOf(minimizingTaskChange)
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {}
+ )
+
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ argThat { info -> info.changes.contains(minimizingTaskChange) },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler)
+ )
+ }
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_CLOSE,
changeMode: Int = WindowManager.TRANSIT_CLOSE,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 315a46fcbd7b..ad266ead774e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -3038,6 +3038,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Assert bounds set to stable bounds
val wct = getLatestToggleResizeDesktopTaskWct()
assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ // Assert event is properly logged
+ verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ motionEvent,
+ task,
+ displayController
+ )
+ verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ motionEvent,
+ task,
+ STABLE_BOUNDS.height(),
+ STABLE_BOUNDS.width(),
+ displayController
+ )
}
@Test
@@ -3082,6 +3097,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
eq(STABLE_BOUNDS),
anyOrNull(),
)
+ // Assert no event is logged
+ verify(desktopModeEventLogger, never()).logTaskResizingStarted(
+ any(), any(), any(), any(), any()
+ )
+ verify(desktopModeEventLogger, never()).logTaskResizingEnded(
+ any(), any(), any(), any(), any(), any(), any()
+ )
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 737439ce3cfe..7f1c1db3207a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -76,6 +76,7 @@ class DesktopTasksTransitionObserverTest {
private val context = mock<Context>()
private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
private val taskRepository = mock<DesktopRepository>()
+ private val mixedHandler = mock<DesktopMixedTransitionHandler>()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
private lateinit var shellInit: ShellInit
@@ -87,7 +88,7 @@ class DesktopTasksTransitionObserverTest {
transitionObserver =
DesktopTasksTransitionObserver(
- context, taskRepository, transitions, shellTaskOrganizer, shellInit
+ context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit
)
}
@@ -106,6 +107,7 @@ class DesktopTasksTransitionObserverTest {
)
verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ verify(mixedHandler).addPendingMixedTransition(any())
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 72950a8dc139..6d37ed766aef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -28,8 +28,11 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
.setContainerLayer()
.setName("FakeLeash")
.build();
+ mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class);
}
@Test
@@ -93,7 +97,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -107,14 +112,16 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
oldAnimator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -145,7 +152,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
// Fullscreen to PiP.
PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
// Apply fraction 1 to compute the end value.
animator.applySurfaceControlTransaction(mLeash, tx, 1);
final Rect rotatedEndBounds = new Rect(endBounds);
@@ -157,7 +165,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
startBounds.set(0, 0, 1000, 500);
endBounds.set(200, 100, 400, 500);
animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
- endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
animator.applySurfaceControlTransaction(mLeash, tx, 1);
rotatedEndBounds.set(endBounds);
rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
@@ -166,6 +175,37 @@ public class PipAnimationControllerTest extends ShellTestCase {
}
@Test
+ public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() {
+ final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
+ final Rect startBounds = new Rect(200, 700, 400, 800);
+ final Rect endBounds = new Rect(0, 0, 500, 1000);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+
+ // Fullscreen task to PiP.
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
+ // Apply fraction 1 to compute the end value.
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+ assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+ animator.mCurrentValue);
+
+ // PiP to fullscreen.
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+ startBounds.set(0, 0, 1000, 500);
+ endBounds.set(200, 100, 400, 500);
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+ assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+ animator.mCurrentValue);
+ }
+
+ @Test
@SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
final Rect baseValue = new Rect(0, 0, 100, 100);
@@ -174,7 +214,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.updateEndValue(endValue2);
@@ -188,7 +229,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
@@ -207,4 +249,126 @@ public class PipAnimationControllerTest extends ShellTestCase {
verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
any(SurfaceControl.Transaction.class), eq(animator));
}
+
+ @Test
+ public void pipTransitionAnimator_overrideMainWindowFrame() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue());
+ assertEquals("Expect start value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is overridden for leave-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_letterboxed_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed();
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_sizeCompat_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat();
+ PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 5ebf5170bf86..59141ca39487 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.content.ComponentName
+import android.graphics.Region
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.InsetsState
@@ -33,6 +34,9 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class CaptionWindowDecorationTests : ShellTestCase() {
+
+ private val exclusionRegion = Region.obtain()
+
@Test
fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
val taskInfo = createTaskInfo()
@@ -50,7 +54,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
@@ -72,7 +77,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
@@ -90,7 +96,8 @@ class CaptionWindowDecorationTests : ShellTestCase() {
true /* isStatusBarVisible */,
false /* isKeyguardVisibleAndOccluded */,
InsetsState(),
- true /* hasGlobalFocus */
+ true /* hasGlobalFocus */,
+ exclusionRegion
)
Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 956100d9bc03..be664f86e9f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -30,6 +30,7 @@ import android.content.Intent
import android.content.Intent.ACTION_MAIN
import android.content.pm.ActivityInfo
import android.graphics.Rect
+import android.graphics.Region
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
@@ -48,6 +49,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
+import android.view.ISystemGestureExclusionListener
import android.view.IWindowManager
import android.view.InputChannel
import android.view.InputMonitor
@@ -84,7 +86,6 @@ import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
-import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
@@ -131,6 +132,7 @@ import org.mockito.Mockito.times
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
@@ -175,7 +177,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockInputMonitorFactory:
DesktopModeWindowDecorViewModel.InputMonitorFactory
@Mock private lateinit var mockShellController: ShellController
- @Mock private lateinit var mockShellExecutor: ShellExecutor
+ private val testShellExecutor = TestShellExecutor()
@Mock private lateinit var mockAppHeaderViewHolderFactory: AppHeaderViewHolder.Factory
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@@ -230,13 +232,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
spyContext = spy(mContext)
doNothing().`when`(spyContext).startActivity(any())
- shellInit = ShellInit(mockShellExecutor)
+ shellInit = ShellInit(testShellExecutor)
windowDecorByTaskIdSpy.clear()
spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
desktopModeEventLogger = mock<DesktopModeEventLogger>()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
spyContext,
- mockShellExecutor,
+ testShellExecutor,
mockMainHandler,
mockMainChoreographer,
bgExecutor,
@@ -1321,11 +1323,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
decoration.mHasGlobalFocus = true
desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
- verify(decoration).relayout(task, true)
+ verify(decoration).relayout(eq(task), eq(true), anyOrNull())
decoration.mHasGlobalFocus = false
desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
- verify(decoration).relayout(task, false)
+ verify(decoration).relayout(eq(task), eq(false), anyOrNull())
}
@Test
@@ -1342,17 +1344,66 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
task.isFocused = true
desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
- verify(decoration).relayout(task, true)
+ verify(decoration).relayout(eq(task), eq(true), anyOrNull())
task.isFocused = false
desktopModeWindowDecorViewModel.onTaskInfoChanged(task)
- verify(decoration).relayout(task, false)
+ verify(decoration).relayout(eq(task), eq(false), anyOrNull())
+ }
+
+ @Test
+ fun testGestureExclusionChanged_updatesDecorations() {
+ val captor = argumentCaptor<ISystemGestureExclusionListener>()
+ verify(mockWindowManager)
+ .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY))
+ val task = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val task2 = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val newRegion = Region.obtain().apply {
+ set(Rect(0, 0, 1600, 80))
+ }
+
+ captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion)
+ testShellExecutor.flushAll()
+
+ verify(task).onExclusionRegionChanged(newRegion)
+ verify(task2).onExclusionRegionChanged(newRegion)
+ }
+
+ @Test
+ fun testGestureExclusionChanged_otherDisplay_skipsDecorationUpdate() {
+ val captor = argumentCaptor<ISystemGestureExclusionListener>()
+ verify(mockWindowManager)
+ .registerSystemGestureExclusionListener(captor.capture(), eq(DEFAULT_DISPLAY))
+ val task = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = DEFAULT_DISPLAY
+ )
+ val task2 = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ displayId = 2
+ )
+ val newRegion = Region.obtain().apply {
+ set(Rect(0, 0, 1600, 80))
+ }
+
+ captor.firstValue.onSystemGestureExclusionChanged(DEFAULT_DISPLAY, newRegion, newRegion)
+ testShellExecutor.flushAll()
+
+ verify(task).onExclusionRegionChanged(newRegion)
+ verify(task2, never()).onExclusionRegionChanged(newRegion)
}
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
taskSurface: SurfaceControl = SurfaceControl(),
requestingImmersive: Boolean = false,
+ displayId: Int = DEFAULT_DISPLAY,
onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
onImmersiveOrRestoreListenerCaptor: KArgumentCaptor<() -> Unit> =
@@ -1376,6 +1427,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
): DesktopModeWindowDecoration {
val decor = setUpMockDecorationForTask(createTask(
windowingMode = windowingMode,
+ displayId = displayId,
requestingImmersive = requestingImmersive
))
onTaskOpening(decor.mTaskInfo, taskSurface)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 41f57ae0fd97..1d2d0f078817 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -64,6 +64,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Region;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
@@ -224,6 +225,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private TestableContext mTestableContext;
private final ShellExecutor mBgExecutor = new TestShellExecutor();
private final AssistContent mAssistContent = new AssistContent();
+ private final Region mExclusionRegion = Region.obtain();
/** Set up run before test class. */
@BeforeClass
@@ -262,8 +264,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
- anyInt(), anyInt()))
+ anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(),
+ anyInt(), anyInt(), anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -283,7 +285,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor =
spy(createWindowDecoration(taskInfo));
- spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion);
// Menus should close if open before the task being invisible causes relayout to return.
verify(spyWindowDecor).closeHandleMenu();
@@ -303,7 +305,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
}
@@ -324,7 +327,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
}
@@ -350,7 +354,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity);
}
@@ -377,12 +382,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity);
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -400,12 +407,39 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isTrue();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_freeformAndTransparentAppearance_limitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isTrue();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -422,12 +456,38 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_freeformButOpaqueAppearance_unlimitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
public void updateRelayoutParams_fullscreen_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -443,12 +503,36 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.hasInputFeatureSpy()).isFalse();
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSIBLE_CUSTOM_HEADERS)
+ public void updateRelayoutParams_fullscreen_unlimitedTouchRegion() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false,
+ /* isStatusBarVisible */ true,
+ /* isKeyguardVisibleAndOccluded */ false,
+ /* inFullImmersiveMode */ false,
+ new InsetsState(),
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
+
+ assertThat(relayoutParams.mLimitTouchRegionToSystemAreas).isFalse();
+ }
+
+ @Test
public void updateRelayoutParams_freeform_inputChannelNeeded() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -464,7 +548,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isFalse();
}
@@ -486,7 +571,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -508,7 +594,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(hasNoInputChannelFeature(relayoutParams)).isTrue();
}
@@ -531,7 +618,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
}
@@ -555,7 +643,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
}
@@ -577,7 +666,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
@@ -601,7 +691,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(
(relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
@@ -631,7 +722,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
insetsState,
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
// Takes status bar inset as padding, ignores caption bar inset.
assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -654,7 +746,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsInsetSource).isFalse();
}
@@ -676,7 +769,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
// Header is always shown because it's assumed the status bar is always visible.
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -698,7 +792,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
}
@@ -719,7 +814,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -740,7 +836,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ false,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -762,7 +859,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -776,7 +874,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ false,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -798,7 +897,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
/* isKeyguardVisibleAndOccluded */ true,
/* inFullImmersiveMode */ true,
new InsetsState(),
- /* hasGlobalFocus= */ true);
+ /* hasGlobalFocus= */ true,
+ mExclusionRegion);
assertThat(relayoutParams.mIsCaptionVisible).isFalse();
}
@@ -809,7 +909,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockTransaction).apply();
verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any());
@@ -824,7 +924,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockTransaction, never()).apply();
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction);
@@ -836,7 +936,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
}
@@ -848,7 +948,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -865,7 +965,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
// Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT)
taskInfo.isResizeable = false;
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
verify(mMockHandler, never()).post(any());
@@ -877,11 +977,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
}
@@ -892,7 +992,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Once for view host, the other for the AppHandle input layer.
verify(mMockHandler, times(2)).post(runnableArgument.capture());
@@ -1132,7 +1232,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
runnableArgument.getValue().run();
// Relayout decor with same captured link
- decor.relayout(taskInfo, true /* hasGlobalFocus */);
+ decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
// Verify handle menu's browser link not set to captured link since link is expired
createHandleMenu(decor);
@@ -1313,7 +1413,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, never()).notifyCaptionChanged(any());
}
@@ -1330,7 +1430,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1357,7 +1457,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
verify(mMockAppHeaderViewHolder, atLeastOnce()).runOnAppChipGlobalLayout(
runnableArgumentCaptor.capture());
runnableArgumentCaptor.getValue().invoke();
@@ -1380,7 +1480,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, false /* hasGlobalFocus */, mExclusionRegion);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
captionStateArgumentCaptor.capture());
@@ -1400,7 +1500,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
createHandleMenu(spyWindowDecor);
verify(mMockCaptionHandleRepository, atLeastOnce()).notifyCaptionChanged(
@@ -1425,7 +1525,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass(
CaptionState.class);
- spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
createHandleMenu(spyWindowDecor);
spyWindowDecor.closeHandleMenu();
@@ -1440,9 +1540,30 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void browserApp_webUriUsedForBrowserApp() {
+ // Make {@link AppToWebUtils#isBrowserApp} return true
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.handleAllWebDataURI = true;
+ resolveInfo.activityInfo = createActivityInfo();
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(List.of(resolveInfo));
+
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+ TEST_URI3 /* generic link */);
+
+ // Verify web uri used for browser applications
+ createHandleMenu(decor);
+ verifyHandleMenuCreated(TEST_URI2);
+ }
+
+
private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
- any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
+ any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
anyInt(), anyInt(), anyInt(), anyInt());
}
@@ -1522,7 +1643,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
if (relayout) {
- windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion);
}
return windowDecor;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index ade17c61eda1..7ec2cbf9460e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -242,7 +242,7 @@ class HandleMenuTest : ShellTestCase() {
private fun createAndShowHandleMenu(
splitPosition: Int? = null,
- forceShowSystemBars: Boolean = false,
+ forceShowSystemBars: Boolean = false
): HandleMenu {
val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) {
R.layout.desktop_mode_app_header
@@ -266,8 +266,9 @@ class HandleMenuTest : ShellTestCase() {
WindowManagerWrapper(mockWindowManager),
layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
- shouldShowChangeAspectRatioButton = false,
- null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ shouldShowChangeAspectRatioButton = false, isBrowserApp = false,
+ null /* openInAppOrBrowserIntent */, captionWidth = HANDLE_WIDTH,
+ captionHeight = 50,
captionX = captionX,
captionY = 0,
)
@@ -278,7 +279,7 @@ class HandleMenuTest : ShellTestCase() {
onNewWindowClickListener = mock(),
onManageWindowsClickListener = mock(),
onChangeAspectRatioClickListener = mock(),
- openInBrowserClickListener = mock(),
+ openInAppOrBrowserClickListener = mock(),
onOpenByDefaultClickListener = mock(),
onCloseMenuClickListener = mock(),
onOutsideTouchListener = mock(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8e0434cb28f7..534803db5fe0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -60,6 +60,7 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -508,7 +509,7 @@ public class WindowDecorationTests extends ShellTestCase {
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */);
+ true /* hasGlobalFocus */, Region.obtain());
verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
}
@@ -525,7 +526,7 @@ public class WindowDecorationTests extends ShellTestCase {
mRelayoutParams.mCaptionTopPadding = 50;
windowDecor.relayout(taskInfo, false /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */);
+ true /* hasGlobalFocus */, Region.obtain());
assertEquals(50, mRelayoutResult.mCaptionTopPadding);
}
@@ -944,7 +945,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
- verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -958,7 +959,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
- verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -973,7 +974,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
assertTrue(decor.mIsKeyguardVisibleAndOccluded);
- verify(decor, times(2)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(2)).relayout(any(), any(), any(), any(), any(), any());
}
@Test
@@ -987,7 +988,7 @@ public class WindowDecorationTests extends ShellTestCase {
decor.onKeyguardStateChanged(false /* visible */, true /* occluding */);
- verify(decor, times(1)).relayout(task, true /* hasGlobalFocus */);
+ verify(decor, times(1)).relayout(any(), any(), any(), any(), any(), any());
}
private ActivityManager.RunningTaskInfo createTaskInfo() {
@@ -1061,9 +1062,16 @@ public class WindowDecorationTests extends ShellTestCase {
surfaceControlViewHostFactory, desktopModeEventLogger);
}
- @Override
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
- relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus);
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus,
+ Region.obtain());
+ }
+
+ @Override
+ void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
+ relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus,
+ displayExclusionRegion);
}
@Override
@@ -1085,11 +1093,13 @@ public class WindowDecorationTests extends ShellTestCase {
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
- boolean applyStartTransactionOnDraw, boolean hasGlobalFocus) {
+ boolean applyStartTransactionOnDraw, boolean hasGlobalFocus,
+ @NonNull Region displayExclusionRegion) {
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
mRelayoutParams.mLayoutResId = R.layout.caption_layout;
mRelayoutParams.mHasGlobalFocus = hasGlobalFocus;
+ mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
index 28c3b3df9b1c..2540236f2ce5 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -125,6 +125,7 @@ public final class AppFunctionException extends Exception {
public AppFunctionException(
int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+ super(errorMessage);
mErrorCode = errorCode;
mErrorMessage = errorMessage;
mExtras = extras;
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index fddcf29b9197..5f84f47b725d 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,9 +33,9 @@ inline bool letter_spacing_justification() {
#endif // __ANDROID__
}
-inline bool typeface_redesign() {
+inline bool typeface_redesign_readonly() {
#ifdef __ANDROID__
- static bool flag = com_android_text_flags_typeface_redesign();
+ static bool flag = com_android_text_flags_typeface_redesign_readonly();
return flag;
#else
return true;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 5ad788c67816..fa27af671be6 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -154,3 +154,13 @@ flag {
description: "API's that enable animated image drawables to use nearest sampling when scaling."
bug: "370523334"
}
+
+flag {
+ name: "remove_vri_sketchy_destroy"
+ namespace: "core_graphics"
+ description: "Remove the eager yet thread-violating destroyHardwareResources in VRI#die"
+ bug: "377057106"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 1510ce1378d8..20acf981d9b9 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -73,7 +73,7 @@ public:
static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
float saveSkewX = paint->getSkFont().getSkewX();
bool savefakeBold = paint->getSkFont().isEmbolden();
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
uint32_t start = layout.getFontRunStart(runIdx);
uint32_t end = layout.getFontRunEnd(runIdx);
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 70e6beda6cb9..5f693462af91 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -86,7 +86,7 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
overallDescent = std::max(overallDescent, extent.descent);
}
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
uint32_t runCount = layout.getFontRunCount();
std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds;
@@ -229,7 +229,7 @@ float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin:
// CriticalNative
static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
return std::isnan(value) ? NO_OVERRIDE : value;
@@ -241,7 +241,7 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon
// CriticalNative
static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
- if (text_feature::typeface_redesign()) {
+ if (text_feature::typeface_redesign_readonly()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
return std::isnan(value) ? NO_OVERRIDE : 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/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e575daeb8d29..2ae89d3300c1 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -18,6 +18,7 @@ package android.media;
import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -890,7 +891,7 @@ import java.util.function.Supplier;
any start codes), and submit it as a <strong>regular</strong> input buffer.
<p>
You will receive an {@link #INFO_OUTPUT_FORMAT_CHANGED} return value from {@link
- #dequeueOutputBuffer dequeueOutputBuffer} or a {@link Callback#onOutputBufferAvailable
+ #dequeueOutputBuffer dequeueOutputBuffer} or a {@link Callback#onOutputFormatChanged
onOutputFormatChanged} callback just after the picture-size change takes place and before any
frames with the new size have been returned.
<p class=note>
@@ -1835,6 +1836,13 @@ final public class MediaCodec {
private static final int CB_CRYPTO_ERROR = 6;
private static final int CB_LARGE_FRAME_OUTPUT_AVAILABLE = 7;
+ /**
+ * Callback ID for when the metrics for this codec have been flushed due to
+ * the start of a new subsession. The associated Java Message object will
+ * contain the flushed metrics as a PersistentBundle in the obj field.
+ */
+ private static final int CB_METRICS_FLUSHED = 8;
+
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -2007,6 +2015,15 @@ final public class MediaCodec {
break;
}
+ case CB_METRICS_FLUSHED:
+ {
+
+ if (GetFlag(() -> android.media.codec.Flags.subsessionMetrics())) {
+ mCallback.onMetricsFlushed(mCodec, (PersistableBundle)msg.obj);
+ }
+ break;
+ }
+
default:
{
break;
@@ -4958,14 +4975,24 @@ final public class MediaCodec {
public native final String getCanonicalName();
/**
- * Return Metrics data about the current codec instance.
+ * Return Metrics data about the current codec instance.
+ * <p>
+ * Call this method after configuration, during execution, or after
+ * the codec has been already stopped.
+ * <p>
+ * Beginning with {@link android.os.Build.VERSION_CODES#B}
+ * this method can be used to get the Metrics data prior to an error.
+ * (e.g. in {@link Callback#onError} or after a method throws
+ * {@link MediaCodec.CodecException}.) Before that, the Metrics data was
+ * cleared on error, resulting in a null return value.
*
* @return a {@link PersistableBundle} containing the set of attributes and values
* available for the media being handled by this instance of MediaCodec
* The attributes are descibed in {@link MetricsConstants}.
*
* Additional vendor-specific fields may also be present in
- * the return value.
+ * the return value. Returns null if there is no Metrics data.
+ *
*/
public PersistableBundle getMetrics() {
PersistableBundle bundle = native_getMetrics();
@@ -5692,6 +5719,27 @@ final public class MediaCodec {
*/
public abstract void onOutputFormatChanged(
@NonNull MediaCodec codec, @NonNull MediaFormat format);
+
+ /**
+ * Called when the metrics for this codec have been flushed due to the
+ * start of a new subsession.
+ * <p>
+ * This can happen when the codec is reconfigured after stop(), or
+ * mid-stream e.g. if the video size changes. When this happens, the
+ * metrics for the previous subsession are flushed, and
+ * {@link MediaCodec#getMetrics} will return the metrics for the
+ * new subsession. This happens just before the {@link Callback#onOutputFormatChanged}
+ * event, so this <b>optional</b> callback is provided to be able to
+ * capture the final metrics for the previous subsession.
+ *
+ * @param codec The MediaCodec object.
+ * @param metrics The flushed metrics for this codec.
+ */
+ @FlaggedApi(FLAG_SUBSESSION_METRICS)
+ public void onMetricsFlushed(
+ @NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
+ // default implementation ignores this callback.
+ }
}
private void postEventFromNative(
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3499c438086d..20108e7369d7 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1771,10 +1771,12 @@ public final class MediaRouter2 {
}
/**
- * A class to control media routing session in media route provider. For example,
- * selecting/deselecting/transferring to routes of a session can be done through this. Instances
- * are created when {@link TransferCallback#onTransfer(RoutingController, RoutingController)} is
- * called, which is invoked after {@link #transferTo(MediaRoute2Info)} is called.
+ * Controls a media routing session.
+ *
+ * <p>Routing controllers wrap a {@link RoutingSessionInfo}, taking care of mapping route ids to
+ * {@link MediaRoute2Info} instances. You can still access the underlying session using {@link
+ * #getRoutingSessionInfo()}, but keep in mind it can be changed by other threads. Changes to
+ * the routing session are notified via {@link ControllerCallback}.
*/
public class RoutingController {
private final Object mControllerLock = new Object();
@@ -1836,7 +1838,9 @@ public final class MediaRouter2 {
}
/**
- * @return the unmodifiable list of currently selected routes
+ * Returns the unmodifiable list of currently selected routes
+ *
+ * @see RoutingSessionInfo#getSelectedRoutes()
*/
@NonNull
public List<MediaRoute2Info> getSelectedRoutes() {
@@ -1848,7 +1852,9 @@ public final class MediaRouter2 {
}
/**
- * @return the unmodifiable list of selectable routes for the session.
+ * Returns the unmodifiable list of selectable routes for the session.
+ *
+ * @see RoutingSessionInfo#getSelectableRoutes()
*/
@NonNull
public List<MediaRoute2Info> getSelectableRoutes() {
@@ -1860,7 +1866,9 @@ public final class MediaRouter2 {
}
/**
- * @return the unmodifiable list of deselectable routes for the session.
+ * Returns the unmodifiable list of deselectable routes for the session.
+ *
+ * @see RoutingSessionInfo#getDeselectableRoutes()
*/
@NonNull
public List<MediaRoute2Info> getDeselectableRoutes() {
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 83a4dd5a682a..3b8cf3fb2909 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -262,7 +262,8 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
- * Gets the provider id of the session.
+ * Gets the provider ID of the session.
+ *
* @hide
*/
@Nullable
@@ -271,7 +272,15 @@ public final class RoutingSessionInfo implements Parcelable {
}
/**
- * Gets the list of IDs of selected routes for the session. It shouldn't be empty.
+ * Gets the list of IDs of selected routes for the session.
+ *
+ * <p>Selected routes are the routes that this session is actively routing media to.
+ *
+ * <p>The behavior of a routing session with multiple selected routes is ultimately defined by
+ * the {@link MediaRoute2ProviderService} implementation. However, typically, it's expected that
+ * all the selected routes of a routing session are playing the same media in sync.
+ *
+ * @return A non-empty list of selected route ids.
*/
@NonNull
public List<String> getSelectedRoutes() {
@@ -280,6 +289,16 @@ public final class RoutingSessionInfo implements Parcelable {
/**
* Gets the list of IDs of selectable routes for the session.
+ *
+ * <p>Selectable routes can be added to a routing session (via {@link
+ * MediaRouter2.RoutingController#selectRoute}) in order to add them to the {@link
+ * #getSelectedRoutes() selected routes}, so that media plays on the newly selected route along
+ * with the other selected routes.
+ *
+ * <p>Not to be confused with {@link #getTransferableRoutes() transferable routes}. Transferring
+ * to a route makes it the sole selected route.
+ *
+ * @return A possibly empty list of selectable route ids.
*/
@NonNull
public List<String> getSelectableRoutes() {
@@ -288,6 +307,17 @@ public final class RoutingSessionInfo implements Parcelable {
/**
* Gets the list of IDs of deselectable routes for the session.
+ *
+ * <p>Deselectable routes can be removed from the {@link #getSelectedRoutes() selected routes},
+ * so that the routing session stops routing to the newly deselected route, but continues on any
+ * remaining selected routes.
+ *
+ * <p>Deselectable routes should be a subset of the {@link #getSelectedRoutes() selected
+ * routes}, meaning not all of the selected routes might be deselectable. For example, one of
+ * the selected routes may be a leader device coordinating group playback, which must always
+ * remain selected while the session is active.
+ *
+ * @return A possibly empty list of deselectable route ids.
*/
@NonNull
public List<String> getDeselectableRoutes() {
@@ -296,6 +326,24 @@ public final class RoutingSessionInfo implements Parcelable {
/**
* Gets the list of IDs of transferable routes for the session.
+ *
+ * <p>Transferring to a route (for example, using {@link MediaRouter2#transferTo}) replaces the
+ * list of {@link #getSelectedRoutes() selected routes} with the target route, causing playback
+ * to move from one route to another.
+ *
+ * <p>Note that this is different from {@link #getSelectableRoutes() selectable routes}, because
+ * selecting a route makes it part of the selected routes, while transferring to a route makes
+ * it the selected route. A route can be both transferable and selectable.
+ *
+ * <p>Note that playback may transfer across routes without the target route being in the list
+ * of transferable routes. This can happen by creating a new routing session to the target
+ * route, and releasing the routing session being transferred from. The difference is that a
+ * transfer to a route in the transferable list can happen with no intervention from the app,
+ * with the route provider taking care of the entire operation. A transfer to a route that is
+ * not in the list of transferable routes (by creating a new session) requires the app to move
+ * the playback state from one device to the other.
+ *
+ * @return A possibly empty list of transferable route ids.
*/
@NonNull
public List<String> getTransferableRoutes() {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index ba2398c12607..7895eb27b372 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -166,3 +166,10 @@ flag {
description: "Allows audio input devices routing and volume control via system settings."
bug: "355684672"
}
+
+flag {
+ name: "enable_mirroring_in_media_router_2"
+ namespace: "media_better_together"
+ description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes."
+ bug: "362507305"
+}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 675c8f80add2..a23845fa17e9 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -100,6 +100,7 @@ package android.nfc {
method public void onHceEventReceived(int);
method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String);
method public void onLaunchHceTapAgainDialog(@NonNull android.nfc.cardemulation.ApduServiceInfo, @NonNull String);
+ method public void onLogEventNotified(@NonNull android.nfc.OemLogItems);
method public void onNdefMessage(@NonNull android.nfc.Tag, @NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onReaderOptionChanged(boolean);
@@ -115,6 +116,27 @@ package android.nfc {
method public int getNfceeId();
}
+ @FlaggedApi("android.nfc.nfc_oem_extension") public final class OemLogItems implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAction();
+ method public int getCallingPid();
+ method @Nullable public byte[] getCommandApdu();
+ method public int getEvent();
+ method @Nullable public byte[] getResponseApdu();
+ method @Nullable public java.time.Instant getRfFieldEventTimeMillis();
+ method @Nullable public android.nfc.Tag getTag();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.OemLogItems> CREATOR;
+ field public static final int EVENT_DISABLE = 2; // 0x2
+ field public static final int EVENT_ENABLE = 1; // 0x1
+ field public static final int EVENT_UNSET = 0; // 0x0
+ field public static final int LOG_ACTION_HCE_DATA = 516; // 0x204
+ field public static final int LOG_ACTION_NFC_TOGGLE = 513; // 0x201
+ field public static final int LOG_ACTION_RF_FIELD_STATE_CHANGED = 1; // 0x1
+ field public static final int LOG_ACTION_SCREEN_STATE_CHANGED = 518; // 0x206
+ field public static final int LOG_ACTION_TAG_DETECTED = 3; // 0x3
+ }
+
@FlaggedApi("android.nfc.nfc_oem_extension") public class RoutingStatus {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultIsoDepRoute();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int getDefaultOffHostRoute();
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 7f1fd15fe68a..b102e873d737 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -18,6 +18,7 @@ package android.nfc;
import android.content.ComponentName;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.NdefMessage;
+import android.nfc.OemLogItems;
import android.nfc.Tag;
import android.os.ResultReceiver;
@@ -51,4 +52,5 @@ interface INfcOemExtensionCallback {
void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category);
+ void onLogEventNotified(in OemLogItems item);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 1bfe71461ac3..abd99bc02f55 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -392,6 +392,12 @@ public final class NfcOemExtension {
* @param category the category of the service
*/
void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category);
+
+ /**
+ * Callback when OEM specified log event are notified.
+ * @param item the log items that contains log information of NFC event.
+ */
+ void onLogEventNotified(@NonNull OemLogItems item);
}
@@ -900,6 +906,12 @@ public final class NfcOemExtension {
handleVoid2ArgCallback(service, category, cb::onLaunchHceTapAgainDialog, ex));
}
+ @Override
+ public void onLogEventNotified(OemLogItems item) throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(item, cb::onLogEventNotified, ex));
+ }
+
private <T> void handleVoidCallback(
T input, Consumer<T> callbackMethod, Executor executor) {
synchronized (mLock) {
diff --git a/nfc/java/android/nfc/OemLogItems.aidl b/nfc/java/android/nfc/OemLogItems.aidl
new file mode 100644
index 000000000000..3bcb445fc7d2
--- /dev/null
+++ b/nfc/java/android/nfc/OemLogItems.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.nfc;
+
+parcelable OemLogItems; \ No newline at end of file
diff --git a/nfc/java/android/nfc/OemLogItems.java b/nfc/java/android/nfc/OemLogItems.java
new file mode 100644
index 000000000000..6671941c1cc9
--- /dev/null
+++ b/nfc/java/android/nfc/OemLogItems.java
@@ -0,0 +1,325 @@
+/*
+ * 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.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+
+/**
+ * A log class for OEMs to get log information of NFC events.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+@SystemApi
+public final class OemLogItems implements Parcelable {
+ /**
+ * Used when RF field state is changed.
+ */
+ public static final int LOG_ACTION_RF_FIELD_STATE_CHANGED = 0X01;
+ /**
+ * Used when NFC is toggled. Event should be set to {@link LogEvent#EVENT_ENABLE} or
+ * {@link LogEvent#EVENT_DISABLE} if this action is used.
+ */
+ public static final int LOG_ACTION_NFC_TOGGLE = 0x0201;
+ /**
+ * Used when sending host routing status.
+ */
+ public static final int LOG_ACTION_HCE_DATA = 0x0204;
+ /**
+ * Used when screen state is changed.
+ */
+ public static final int LOG_ACTION_SCREEN_STATE_CHANGED = 0x0206;
+ /**
+ * Used when tag is detected.
+ */
+ public static final int LOG_ACTION_TAG_DETECTED = 0x03;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "LOG_ACTION_" }, value = {
+ LOG_ACTION_RF_FIELD_STATE_CHANGED,
+ LOG_ACTION_NFC_TOGGLE,
+ LOG_ACTION_HCE_DATA,
+ LOG_ACTION_SCREEN_STATE_CHANGED,
+ LOG_ACTION_TAG_DETECTED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LogAction {}
+
+ /**
+ * Represents the event is not set.
+ */
+ public static final int EVENT_UNSET = 0;
+ /**
+ * Represents nfc enable is called.
+ */
+ public static final int EVENT_ENABLE = 1;
+ /**
+ * Represents nfc disable is called.
+ */
+ public static final int EVENT_DISABLE = 2;
+ /** @hide */
+ @IntDef(prefix = { "EVENT_" }, value = {
+ EVENT_UNSET,
+ EVENT_ENABLE,
+ EVENT_DISABLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LogEvent {}
+ private int mAction;
+ private int mEvent;
+ private int mCallingPid;
+ private byte[] mCommandApdus;
+ private byte[] mResponseApdus;
+ private Instant mRfFieldOnTime;
+ private Tag mTag;
+
+ /** @hide */
+ public OemLogItems(@LogAction int action, @LogEvent int event, int callingPid,
+ byte[] commandApdus, byte[] responseApdus, Instant rfFieldOnTime,
+ Tag tag) {
+ mAction = action;
+ mEvent = event;
+ mTag = tag;
+ mCallingPid = callingPid;
+ mCommandApdus = commandApdus;
+ mResponseApdus = responseApdus;
+ mRfFieldOnTime = rfFieldOnTime;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable
+ * instance's marshaled representation. For example, if the object will
+ * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+ * the return value of this method must include the
+ * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+ *
+ * @return a bitmask indicating the set of special object types marshaled
+ * by this Parcelable object instance.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @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.writeInt(mAction);
+ dest.writeInt(mEvent);
+ dest.writeInt(mCallingPid);
+ dest.writeInt(mCommandApdus.length);
+ dest.writeByteArray(mCommandApdus);
+ dest.writeInt(mResponseApdus.length);
+ dest.writeByteArray(mResponseApdus);
+ dest.writeLong(mRfFieldOnTime.getEpochSecond());
+ dest.writeInt(mRfFieldOnTime.getNano());
+ dest.writeParcelable(mTag, 0);
+ }
+
+ /** @hide */
+ public static class Builder {
+ private final OemLogItems mItem;
+
+ public Builder(@LogAction int type) {
+ mItem = new OemLogItems(type, EVENT_UNSET, 0, new byte[0], new byte[0], null, null);
+ }
+
+ /** Setter of the log action. */
+ public OemLogItems.Builder setAction(@LogAction int action) {
+ mItem.mAction = action;
+ return this;
+ }
+
+ /** Setter of the log calling event. */
+ public OemLogItems.Builder setCallingEvent(@LogEvent int event) {
+ mItem.mEvent = event;
+ return this;
+ }
+
+ /** Setter of the log calling Pid. */
+ public OemLogItems.Builder setCallingPid(int pid) {
+ mItem.mCallingPid = pid;
+ return this;
+ }
+
+ /** Setter of APDU command. */
+ public OemLogItems.Builder setApduCommand(byte[] apdus) {
+ mItem.mCommandApdus = apdus;
+ return this;
+ }
+
+ /** Setter of RF field on time. */
+ public OemLogItems.Builder setRfFieldOnTime(Instant time) {
+ mItem.mRfFieldOnTime = time;
+ return this;
+ }
+
+ /** Setter of APDU response. */
+ public OemLogItems.Builder setApduResponse(byte[] apdus) {
+ mItem.mResponseApdus = apdus;
+ return this;
+ }
+
+ /** Setter of dispatched tag. */
+ public OemLogItems.Builder setTag(Tag tag) {
+ mItem.mTag = tag;
+ return this;
+ }
+
+ /** Builds an {@link OemLogItems} instance. */
+ public OemLogItems build() {
+ return mItem;
+ }
+ }
+
+ /**
+ * Gets the action of this log.
+ * @return one of {@link LogAction}
+ */
+ @LogAction
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Gets the event of this log. This will be set to {@link LogEvent#EVENT_ENABLE} or
+ * {@link LogEvent#EVENT_DISABLE} only when action is set to
+ * {@link LogAction#LOG_ACTION_NFC_TOGGLE}
+ * @return one of {@link LogEvent}
+ */
+ @LogEvent
+ public int getEvent() {
+ return mEvent;
+ }
+
+ /**
+ * Gets the calling Pid of this log. This field will be set only when action is set to
+ * {@link LogAction#LOG_ACTION_NFC_TOGGLE}
+ * @return calling Pid
+ */
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ /**
+ * Gets the command APDUs of this log. This field will be set only when action is set to
+ * {@link LogAction#LOG_ACTION_HCE_DATA}
+ * @return a byte array of command APDUs with the same format as
+ * {@link android.nfc.cardemulation.HostApduService#sendResponseApdu(byte[])}
+ */
+ @Nullable
+ public byte[] getCommandApdu() {
+ return mCommandApdus;
+ }
+
+ /**
+ * Gets the response APDUs of this log. This field will be set only when action is set to
+ * {@link LogAction#LOG_ACTION_HCE_DATA}
+ * @return a byte array of response APDUs with the same format as
+ * {@link android.nfc.cardemulation.HostApduService#sendResponseApdu(byte[])}
+ */
+ @Nullable
+ public byte[] getResponseApdu() {
+ return mResponseApdus;
+ }
+
+ /**
+ * Gets the RF field event time in this log in millisecond. This field will be set only when
+ * action is set to {@link LogAction#LOG_ACTION_RF_FIELD_STATE_CHANGED}
+ * @return an {@link Instant} of RF field event time.
+ */
+ @Nullable
+ public Instant getRfFieldEventTimeMillis() {
+ return mRfFieldOnTime;
+ }
+
+ /**
+ * Gets the tag of this log. This field will be set only when action is set to
+ * {@link LogAction#LOG_ACTION_TAG_DETECTED}
+ * @return a detected {@link Tag} in {@link #LOG_ACTION_TAG_DETECTED} case. Return
+ * null otherwise.
+ */
+ @Nullable
+ public Tag getTag() {
+ return mTag;
+ }
+
+ private String byteToHex(byte[] bytes) {
+ char[] HexArray = "0123456789ABCDEF".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = HexArray[v >>> 4];
+ hexChars[j * 2 + 1] = HexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ @Override
+ public String toString() {
+ return "[mCommandApdus: "
+ + ((mCommandApdus != null) ? byteToHex(mCommandApdus) : "null")
+ + "[mResponseApdus: "
+ + ((mResponseApdus != null) ? byteToHex(mResponseApdus) : "null")
+ + ", mCallingApi= " + mEvent
+ + ", mAction= " + mAction
+ + ", mCallingPId = " + mCallingPid
+ + ", mRfFieldOnTime= " + mRfFieldOnTime;
+ }
+ private OemLogItems(Parcel in) {
+ this.mAction = in.readInt();
+ this.mEvent = in.readInt();
+ this.mCallingPid = in.readInt();
+ this.mCommandApdus = new byte[in.readInt()];
+ in.readByteArray(this.mCommandApdus);
+ this.mResponseApdus = new byte[in.readInt()];
+ in.readByteArray(this.mResponseApdus);
+ this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());
+ this.mTag = in.readParcelable(Tag.class.getClassLoader(), Tag.class);
+ }
+
+ public static final @NonNull Parcelable.Creator<OemLogItems> CREATOR =
+ new Parcelable.Creator<OemLogItems>() {
+ @Override
+ public OemLogItems createFromParcel(Parcel in) {
+ return new OemLogItems(in);
+ }
+
+ @Override
+ public OemLogItems[] newArray(int size) {
+ return new OemLogItems[size];
+ }
+ };
+
+}
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/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 843d2aadf333..cd03dd7ca1b3 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -202,6 +202,12 @@ open class KeyedDataObservable<K> : KeyedObservable<K> {
entry.value.execute { observer.onKeyChanged(key, reason) }
}
}
+
+ fun hasAnyObserver(): Boolean {
+ synchronized(observers) { if (observers.isNotEmpty()) return true }
+ synchronized(keyedObservers) { if (keyedObservers.isNotEmpty()) return true }
+ return false
+ }
}
/** [KeyedObservable] with no-op implementations for all interfaces. */
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
index 94d373bed0a5..bde4217b3962 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt
@@ -136,6 +136,18 @@ class PreferenceHierarchy internal constructor(metadata: PreferenceMetadata) :
for (it in children) action(it)
}
+ /** Traversals preference hierarchy recursively and applies given action. */
+ fun forEachRecursively(action: (PreferenceHierarchyNode) -> Unit) {
+ action(this)
+ for (child in children) {
+ if (child is PreferenceHierarchy) {
+ child.forEachRecursively(action)
+ } else {
+ action(child)
+ }
+ }
+ }
+
/** Traversals preference hierarchy and applies given action. */
suspend fun forEachAsync(action: suspend (PreferenceHierarchyNode) -> Unit) {
for (it in children) action(it)
@@ -157,18 +169,7 @@ class PreferenceHierarchy internal constructor(metadata: PreferenceMetadata) :
/** Returns all the [PreferenceHierarchyNode]s appear in the hierarchy. */
fun getAllPreferences(): List<PreferenceHierarchyNode> =
- mutableListOf<PreferenceHierarchyNode>().also { getAllPreferences(it) }
-
- private fun getAllPreferences(result: MutableList<PreferenceHierarchyNode>) {
- result.add(this)
- for (child in children) {
- if (child is PreferenceHierarchy) {
- child.getAllPreferences(result)
- } else {
- result.add(child)
- }
- }
- }
+ mutableListOf<PreferenceHierarchyNode>().apply { forEachRecursively { add(it) } }
}
/**
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
index 41a626fe8efa..991d5b7791e9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt
@@ -32,7 +32,7 @@ import com.android.settingslib.widget.SettingsBasePreferenceFragment
open class PreferenceFragment :
SettingsBasePreferenceFragment(), PreferenceScreenProvider, PreferenceScreenBindingKeyProvider {
- private var preferenceScreenBindingHelper: PreferenceScreenBindingHelper? = null
+ protected var preferenceScreenBindingHelper: PreferenceScreenBindingHelper? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceScreen = createPreferenceScreen()
@@ -129,7 +129,9 @@ open class PreferenceFragment :
}
protected fun getPreferenceKeysInHierarchy(): Set<String> =
- preferenceScreenBindingHelper?.getPreferences()?.map { it.metadata.key }?.toSet() ?: setOf()
+ preferenceScreenBindingHelper?.let {
+ mutableSetOf<String>().apply { it.forEachRecursively { add(it.metadata.key) } }
+ } ?: setOf()
companion object {
private const val TAG = "PreferenceFragment"
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 022fb1dbe99c..fbe892710d40 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -143,7 +143,8 @@ class PreferenceScreenBindingHelper(
}
}
- fun getPreferences() = preferenceHierarchy.getAllPreferences()
+ fun forEachRecursively(action: (PreferenceHierarchyNode) -> Unit) =
+ preferenceHierarchy.forEachRecursively(action)
fun onCreate() {
for (preference in lifecycleAwarePreferences) {
@@ -191,11 +192,11 @@ class PreferenceScreenBindingHelper(
companion object {
/** Preference value is changed. */
- private const val CHANGE_REASON_VALUE = 0
+ const val CHANGE_REASON_VALUE = 0
/** Preference state (title/summary, enable state, etc.) is changed. */
- private const val CHANGE_REASON_STATE = 1
+ const val CHANGE_REASON_STATE = 1
/** Dependent preference state is changed. */
- private const val CHANGE_REASON_DEPENDENT = 2
+ const val CHANGE_REASON_DEPENDENT = 2
/** Updates preference screen that has incomplete hierarchy. */
@JvmStatic
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/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index edd49c5a8fb7..0209eb8c3fbf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -21,6 +21,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
+import android.os.DeadObjectException
import android.os.IBinder
import android.os.IInterface
import android.os.RemoteException
@@ -52,6 +53,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterIsInstance
@@ -304,6 +306,14 @@ class DeviceSettingServiceConnection(
service.registerDeviceSettingsListener(deviceInfo, listener)
awaitClose { service.unregisterDeviceSettingsListener(deviceInfo, listener) }
}
+ .catch { e ->
+ if (e is DeadObjectException) {
+ Log.e(TAG, "DeadObjectException happens when registering listener.", e)
+ emit(listOf())
+ } else {
+ throw e
+ }
+ }
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
}
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 0724410a2954..7b6321d1cc7d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -962,6 +962,10 @@
<!-- 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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 207ed71c955d..3df96030d221 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -267,7 +267,7 @@ flag {
flag {
name: "dual_shade"
namespace: "systemui"
- description: "Enables the BC25 Dual Shade (go/bc25-dual-shade-design)."
+ description: "Enables Dual Shade (go/dual-shade-design-doc)."
bug: "337259436"
}
@@ -1360,16 +1360,6 @@ flag {
}
flag {
- name: "notification_pulsing_fix"
- namespace: "systemui"
- description: "Allow showing new pulsing notifications when the device is already pulsing."
- bug: "335560575"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "media_lockscreen_launch_animation"
namespace : "systemui"
description : "Enable the origin launch animation for UMO when opening on top of lockscreen."
@@ -1784,3 +1774,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "keyguard_transition_force_finish_on_screen_off"
+ namespace: "systemui"
+ description: "Forces KTF transitions to finish if the screen turns all the way off."
+ bug: "331636736"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 000000000000..651e401681c6
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
new file mode 100644
index 000000000000..10e630d44488
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+ <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index c574d1fc674b..7feea6e5e8dd 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -33,4 +33,10 @@
<dimen name="small_clock_height">114dp</dimen>
<dimen name="small_clock_padding_top">28dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
+
+ <!-- When large clock is showing, offset the smartspace by this amount -->
+ <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+ <!--Dimens used in both lockscreen preview and smartspace -->
+ <dimen name="date_weather_view_height">24dp</dimen>
+ <dimen name="enhanced_smartspace_height">104dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index a4782acaed9b..ee21ea6ee126 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -55,10 +55,7 @@ class FlexClockFaceController(
override val view: View
get() = layerController.view
- override val config =
- ClockFaceConfig(
- hasCustomPositionUpdatedAnimation = false // TODO(b/364673982)
- )
+ override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
override var theme = ThemeConfig(true, assets.seedColor)
@@ -96,6 +93,19 @@ class FlexClockFaceController(
layerController.view.layoutParams = lp
}
+ /** See documentation at [FlexClockView.offsetGlyphsForStepClockAnimation]. */
+ private fun offsetGlyphsForStepClockAnimation(
+ clockStartLeft: Int,
+ direction: Int,
+ fraction: Float
+ ) {
+ (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
+ clockStartLeft,
+ direction,
+ fraction,
+ )
+ }
+
override val layout: ClockFaceLayout =
DefaultClockFaceLayout(view).apply {
views[0].id =
@@ -248,10 +258,12 @@ class FlexClockFaceController(
override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
layerController.animations.onPositionUpdated(fromLeft, direction, fraction)
+ if (isLargeClock) offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
}
override fun onPositionUpdated(distance: Float, fraction: Float) {
layerController.animations.onPositionUpdated(distance, fraction)
+ // TODO(b/378128811) port stepping animation
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index d86c0d664590..593eba9d05cc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shared.clocks.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
+import android.util.MathUtils.constrainedMap
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
@@ -50,6 +51,8 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
)
}
+ private val digitOffsets = mutableMapOf<Int, Float>()
+
override fun addView(child: View?) {
super.addView(child)
(child as SimpleDigitalClockTextView).digitTranslateAnimator =
@@ -76,7 +79,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0)
digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y)
digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize)
- digitLeftTopMap.forEach { _, point ->
+ digitLeftTopMap.forEach { (_, point) ->
point.x += abs(aodTranslate.x)
point.y += abs(aodTranslate.y)
}
@@ -89,11 +92,17 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- digitalClockTextViewMap.forEach { (id, _) ->
- val textView = digitalClockTextViewMap[id]!!
- canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat())
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ // save canvas location in anticipation of restoration later
+ canvas.save()
+ val xTranslateAmount =
+ digitOffsets.getOrDefault(id, 0f) + digitLeftTopMap[id]!!.x.toFloat()
+ // move canvas to location that the textView would like
+ canvas.translate(xTranslateAmount, digitLeftTopMap[id]!!.y.toFloat())
+ // draw the textView at the location of the canvas above
textView.draw(canvas)
- canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat())
+ // reset the canvas location back to 0 without drawing
+ canvas.restore()
}
}
@@ -157,10 +166,108 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
}
}
+ /**
+ * Offsets the textViews of the clock for the step clock animation.
+ *
+ * The animation makes the textViews of the clock move at different speeds, when the clock is
+ * moving horizontally.
+ *
+ * @param clockStartLeft the [getLeft] position of the clock, before it started moving.
+ * @param clockMoveDirection the direction in which it is moving. A positive number means right,
+ * and negative means left.
+ * @param moveFraction fraction of the clock movement. 0 means it is at the beginning, and 1
+ * means it finished moving.
+ */
+ fun offsetGlyphsForStepClockAnimation(
+ clockStartLeft: Int,
+ clockMoveDirection: Int,
+ moveFraction: Float,
+ ) {
+ val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
+ // The sign of moveAmountDeltaForDigit is already set here
+ // we can interpret (left - clockStartLeft) as (destinationPosition - originPosition)
+ // so we no longer need to multiply direct sign to moveAmountDeltaForDigit
+ val currentMoveAmount = left - clockStartLeft
+ for (i in 0 until NUM_DIGITS) {
+ val mapIndexToId =
+ when (i) {
+ 0 -> R.id.HOUR_FIRST_DIGIT
+ 1 -> R.id.HOUR_SECOND_DIGIT
+ 2 -> R.id.MINUTE_FIRST_DIGIT
+ 3 -> R.id.MINUTE_SECOND_DIGIT
+ else -> -1
+ }
+ val digitFraction =
+ getDigitFraction(
+ digit = i,
+ isMovingToCenter = isMovingToCenter,
+ fraction = moveFraction,
+ )
+ // left here is the final left position after the animation is done
+ val moveAmountForDigit = currentMoveAmount * digitFraction
+ var moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
+ if (isMovingToCenter && moveAmountForDigit < 0) moveAmountDeltaForDigit *= -1
+ digitOffsets[mapIndexToId] = moveAmountDeltaForDigit
+ invalidate()
+ }
+ }
+
+ private val moveToCenterDelays: List<Int>
+ get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
+
+ private val moveToSideDelays: List<Int>
+ get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
+
+ private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
+ // The delay for the digit, in terms of fraction.
+ // (i.e. the digit should not move during 0.0 - 0.1).
+ val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+ val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
+ return MOVE_INTERPOLATOR.getInterpolation(
+ constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ digitInitialDelay,
+ /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+ /* value= */ fraction,
+ )
+ )
+ }
+
companion object {
val AOD_TRANSITION_DURATION = 750L
val CHARGING_TRANSITION_DURATION = 300L
+ // Calculate the positions of all of the digits...
+ // Offset each digit by, say, 0.1
+ // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+ // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+ // from 0.3 - 1.0.
+ private const val NUM_DIGITS = 4
+
+ // Delays. Each digit's animation should have a slight delay, so we get a nice
+ // "stepping" effect. When moving right, the second digit of the hour should move first.
+ // When moving left, the first digit of the hour should move first. The lists encode
+ // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+ // by delayMultiplier.
+ private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+ private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+ // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+ // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+ // before moving).
+ //
+ // The current specs dictate that each digit should have a 33ms gap between them. The
+ // overall time is 1s right now.
+ private const val MOVE_DIGIT_STEP = 0.033f
+
+ // Constants for the animation
+ private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+
+ // Total available transition time for each digit, taking into account the step. If step is
+ // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+ private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+
// Use the sign of targetTranslation to control the direction of digit translation
fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
val outPoint = Point(targetTranslation)
@@ -169,17 +276,14 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
outPoint.x *= -1
outPoint.y *= -1
}
-
R.id.HOUR_SECOND_DIGIT -> {
outPoint.x *= 1
outPoint.y *= -1
}
-
R.id.MINUTE_FIRST_DIGIT -> {
outPoint.x *= -1
outPoint.y *= 1
}
-
R.id.MINUTE_SECOND_DIGIT -> {
outPoint.x *= 1
outPoint.y *= 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index e1421691a92d..58fe2c9cbe57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -36,6 +36,7 @@ import org.mockito.junit.MockitoJUnit
private const val USER_ID = 22
private const val OWNER_ID = 10
+private const val PASSWORD_ID = 30
private const val OPERATION_ID = 100L
private const val MAX_ATTEMPTS = 5
@@ -247,7 +248,11 @@ class CredentialInteractorImplTest : SysuiTestCase() {
private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin =
BiometricPromptRequest.Credential.Pin(
promptInfo(),
- BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner),
+ BiometricUserInfo(
+ userId = USER_ID,
+ deviceCredentialOwnerId = credentialOwner,
+ userIdForPasswordEntry = PASSWORD_ID,
+ ),
BiometricOperationInfo(OPERATION_ID),
)
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 81d3f7232c78..f0d79bb83652 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
@@ -17,6 +17,8 @@
package com.android.systemui.deviceentry.domain.interactor
import android.content.pm.UserInfo
+import android.os.PowerManager
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -31,6 +33,7 @@ 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.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -38,12 +41,18 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -225,6 +234,8 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
@Test
fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() =
testScope.runTest {
+ setLockAfterScreenTimeout(0)
+ kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
@@ -240,8 +251,52 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
}
@Test
+ fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_afterDelay() =
+ testScope.runTest {
+ val delay = 5000
+ setLockAfterScreenTimeout(delay)
+ kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ advanceTimeBy(delay.toLong())
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ @Test
+ fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_powerButtonLocksInstantly() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(5000)
+ kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = true
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ @Test
fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
testScope.runTest {
+ setLockAfterScreenTimeout(0)
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
@@ -450,6 +505,98 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
.isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
}
+ @Test
+ fun deviceUnlockStatus_locksImmediately_whenDreamStarts_noTimeout() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(0)
+ val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+ unlockDevice()
+
+ startDreaming()
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun deviceUnlockStatus_locksWithDelay_afterDreamStarts_withTimeout() =
+ testScope.runTest {
+ val delay = 5000
+ setLockAfterScreenTimeout(delay)
+ val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+ unlockDevice()
+
+ startDreaming()
+ assertThat(isUnlocked).isTrue()
+
+ advanceTimeBy(delay - 1L)
+ assertThat(isUnlocked).isTrue()
+
+ advanceTimeBy(1L)
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun deviceUnlockStatus_doesNotLockWithDelay_whenDreamStopsBeforeTimeout() =
+ testScope.runTest {
+ val delay = 5000
+ setLockAfterScreenTimeout(delay)
+ val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+ unlockDevice()
+
+ startDreaming()
+ assertThat(isUnlocked).isTrue()
+
+ advanceTimeBy(delay - 1L)
+ assertThat(isUnlocked).isTrue()
+
+ stopDreaming()
+ assertThat(isUnlocked).isTrue()
+
+ advanceTimeBy(1L)
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_doesNotLock_whenDreamStarts_ifNotInteractive() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(0)
+ val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+ unlockDevice()
+
+ startDreaming()
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ private fun TestScope.unlockDevice() {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason")
+ runCurrent()
+ }
+
+ private fun setLockAfterScreenTimeout(timeoutMs: Int) {
+ kosmos.fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ timeoutMs,
+ kosmos.selectedUserInteractor.getSelectedUserId(),
+ )
+ }
+
+ private fun TestScope.startDreaming() {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ runCurrent()
+ }
+
+ private fun TestScope.stopDreaming() {
+ kosmos.fakeKeyguardRepository.setDreaming(false)
+ runCurrent()
+ }
+
private fun TestScope.verifyRestrictionReasonsForAuthFlags(
vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index bfe89de6229d..3d5498b61471 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -16,11 +16,14 @@
package com.android.systemui.keyguard.data.repository
+import android.animation.Animator
import android.animation.ValueAnimator
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -41,6 +44,8 @@ import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.UUID
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.dropWhile
@@ -53,6 +58,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -65,6 +71,8 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardTransitionRepository
private lateinit var runner: KeyguardTransitionRunner
+ private val animatorListener = mock<Animator.AnimatorListener>()
+
@Before
fun setUp() {
underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main)
@@ -80,7 +88,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
runner.startTransition(
this,
TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
- maxFrames = 100
+ maxFrames = 100,
)
assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
@@ -107,7 +115,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
LOCKSCREEN,
AOD,
getAnimator(),
- TransitionModeOnCanceled.LAST_VALUE
+ TransitionModeOnCanceled.LAST_VALUE,
),
)
@@ -142,7 +150,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
LOCKSCREEN,
AOD,
getAnimator(),
- TransitionModeOnCanceled.RESET
+ TransitionModeOnCanceled.RESET,
),
)
@@ -177,7 +185,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
LOCKSCREEN,
AOD,
getAnimator(),
- TransitionModeOnCanceled.REVERSE
+ TransitionModeOnCanceled.REVERSE,
),
)
@@ -476,6 +484,49 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
assertThat(steps.size).isEqualTo(3)
}
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF)
+ fun forceFinishCurrentTransition_noFurtherStepsEmitted() =
+ testScope.runTest {
+ val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
+
+ var sentForceFinish = false
+
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 100,
+ // Force-finish on the second frame.
+ frameCallback = { frameNumber ->
+ if (!sentForceFinish && frameNumber > 1) {
+ testScope.launch { underTest.forceFinishCurrentTransition() }
+ sentForceFinish = true
+ }
+ },
+ )
+
+ val lastTwoRunningSteps =
+ steps.filter { it.transitionState == TransitionState.RUNNING }.takeLast(2)
+
+ // Make sure we stopped emitting RUNNING steps early, but then emitted a final 1f step.
+ assertTrue(lastTwoRunningSteps[0].value < 0.5f)
+ assertTrue(lastTwoRunningSteps[1].value == 1f)
+
+ assertEquals(steps.last().from, AOD)
+ assertEquals(steps.last().to, LOCKSCREEN)
+ assertEquals(steps.last().transitionState, TransitionState.FINISHED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF)
+ fun forceFinishCurrentTransition_noTransitionStarted_noStepsEmitted() =
+ testScope.runTest {
+ val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
+
+ underTest.forceFinishCurrentTransition()
+ assertEquals(0, steps.size)
+ }
+
private fun listWithStep(
step: BigDecimal,
start: BigDecimal = BigDecimal.ZERO,
@@ -505,7 +556,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
to,
fractions[0].toFloat(),
TransitionState.STARTED,
- OWNER_NAME
+ OWNER_NAME,
)
)
fractions.forEachIndexed { index, fraction ->
@@ -519,7 +570,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
to,
fraction.toFloat(),
TransitionState.RUNNING,
- OWNER_NAME
+ OWNER_NAME,
)
)
}
@@ -538,6 +589,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
setDuration(10)
+ addListener(animatorListener)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 8914c80cdd5e..ae2a5c5fe501 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -34,6 +34,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -60,10 +62,13 @@ import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -96,7 +101,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
testScope = testScope,
- throughTransitionState = TransitionState.RUNNING
+ throughTransitionState = TransitionState.RUNNING,
)
powerInteractor.onCameraLaunchGestureDetected()
@@ -134,7 +139,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
testScope = testScope,
- throughTransitionState = TransitionState.RUNNING
+ throughTransitionState = TransitionState.RUNNING,
)
powerInteractor.onCameraLaunchGestureDetected()
@@ -182,21 +187,12 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
runCurrent()
- assertThat(values)
- .containsExactly(
- false,
- true,
- )
+ assertThat(values).containsExactly(false, true)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
runCurrent()
- assertThat(values)
- .containsExactly(
- false,
- true,
- false,
- )
+ assertThat(values).containsExactly(false, true, false)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
runCurrent()
@@ -228,7 +224,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
from = KeyguardState.GONE,
to = KeyguardState.AOD,
testScope = testScope,
- throughTransitionState = TransitionState.RUNNING
+ throughTransitionState = TransitionState.RUNNING,
)
powerInteractor.onCameraLaunchGestureDetected()
@@ -242,10 +238,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
testScope = testScope,
)
- assertThat(values)
- .containsExactly(
- false,
- )
+ assertThat(values).containsExactly(false)
}
@Test
@@ -263,7 +256,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
from = KeyguardState.UNDEFINED,
to = KeyguardState.AOD,
testScope = testScope,
- throughTransitionState = TransitionState.RUNNING
+ throughTransitionState = TransitionState.RUNNING,
)
powerInteractor.onCameraLaunchGestureDetected()
@@ -278,10 +271,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
testScope = testScope,
)
- assertThat(values)
- .containsExactly(
- false,
- )
+ assertThat(values).containsExactly(false)
}
@Test
@@ -304,8 +294,19 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
assertThat(occludingActivityWillDismissKeyguard).isTrue()
// Re-lock device:
- kosmos.powerInteractor.setAsleepForTest()
- runCurrent()
+ lockDevice()
assertThat(occludingActivityWillDismissKeyguard).isFalse()
}
+
+ private suspend fun TestScope.lockDevice() {
+ kosmos.powerInteractor.setAsleepForTest()
+ advanceTimeBy(
+ kosmos.userAwareSecureSettingsRepository
+ .getInt(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+ )
+ .toLong()
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index ecc62e908a4f..87ab3c89a671 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -69,7 +69,9 @@ class ClockSectionTest : SysuiTestCase() {
get() =
kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() +
context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ context.resources.getDimensionPixelSize(
+ customR.dimen.keyguard_smartspace_top_offset
+ )
private val LARGE_CLOCK_TOP
get() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 1abb441439fe..5798e0776c4f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -21,6 +21,7 @@ import android.animation.ValueAnimator
import android.view.Choreographer.FrameCallback
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -35,9 +36,8 @@ import org.junit.Assert.fail
* Gives direct control over ValueAnimator, in order to make transition tests deterministic. See
* [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly.
*/
-class KeyguardTransitionRunner(
- val repository: KeyguardTransitionRepository,
-) : AnimationFrameCallbackProvider {
+class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
+ AnimationFrameCallbackProvider {
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
@@ -48,7 +48,12 @@ class KeyguardTransitionRunner(
* For transitions being directed by an animator. Will control the number of frames being
* generated so the values are deterministic.
*/
- suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) {
+ suspend fun startTransition(
+ scope: CoroutineScope,
+ info: TransitionInfo,
+ maxFrames: Int = 100,
+ frameCallback: Consumer<Long>? = null,
+ ) {
// AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main
// thread
withContext(Dispatchers.Main) {
@@ -62,7 +67,12 @@ class KeyguardTransitionRunner(
isTerminated = frameNumber >= maxFrames
if (!isTerminated) {
- withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
+ try {
+ withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
+ frameCallback?.accept(frameNumber)
+ } catch (e: IllegalStateException) {
+ e.printStackTrace()
+ }
}
}
}
@@ -90,9 +100,13 @@ class KeyguardTransitionRunner(
override fun postFrameCallback(cb: FrameCallback) {
frames.value = Pair(frameCount++, cb)
}
+
override fun postCommitCallback(runnable: Runnable) {}
+
override fun getFrameTime() = frameCount
+
override fun getFrameDelay() = 1L
+
override fun setFrameDelay(delay: Long) {}
companion object {
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/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 3910903af4aa..ae7c44e9b146 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -35,7 +35,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class EditTileListStateTest : SysuiTestCase() {
- private val underTest = EditTileListState(TestEditTiles, 4)
+ private val underTest = EditTileListState(TestEditTiles, columns = 4, largeTilesSpan = 2)
@Test
fun startDrag_listHasSpacers() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 0729e2fcd35f..03c1f92aad4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -93,6 +93,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
private static final String CARD_DESCRIPTION = "•••• 1234";
private static final Icon CARD_IMAGE =
Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
+ private static final Icon INVALID_CARD_IMAGE =
+ Icon.createWithContentUri("content://media/external/images/media");
private static final int PRIMARY_USER_ID = 0;
private static final int SECONDARY_USER_ID = 10;
@@ -444,6 +446,14 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
}
@Test
+ public void testQueryCards_invalidDrawable_noSideViewDrawable() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ setUpInvalidWalletCard(/* hasCard= */ true);
+
+ assertNull(mTile.getState().sideViewCustomDrawable);
+ }
+
+ @Test
public void testQueryCards_error_notUpdateSideViewDrawable() {
String errorMessage = "getWalletCardsError";
GetWalletCardsError error = new GetWalletCardsError(CARD_IMAGE, errorMessage);
@@ -503,9 +513,33 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
}
+ private void setUpInvalidWalletCard(boolean hasCard) {
+ GetWalletCardsResponse response =
+ new GetWalletCardsResponse(
+ hasCard
+ ? Collections.singletonList(createInvalidWalletCard(mContext))
+ : Collections.EMPTY_LIST, 0);
+
+ mTile.handleSetListening(true);
+
+ verify(mController).queryWalletCards(mCallbackCaptor.capture());
+
+ mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+ }
+
private WalletCard createWalletCard(Context context) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
}
+
+ private WalletCard createInvalidWalletCard(Context context) {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+ return new WalletCard.Builder(
+ CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
+ }
+
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 3be8a380b191..b5f005cdc706 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.scene
+import android.provider.Settings
import android.telephony.TelephonyManager
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,6 +43,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
@@ -64,6 +66,7 @@ import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.telecom.mockTelecomManager
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -72,6 +75,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
@@ -541,7 +545,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.isTrue()
powerInteractor.setAsleepForTest()
- testScope.runCurrent()
+ testScope.advanceTimeBy(
+ kosmos.userAwareSecureSettingsRepository
+ .getInt(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+ )
+ .toLong()
+ )
powerInteractor.setAwakeForTest()
testScope.runCurrent()
@@ -631,14 +642,25 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
/** Changes device wakefulness state from awake to asleep, going through intermediary states. */
- private fun Kosmos.putDeviceToSleep() {
+ private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) {
val wakefulnessModel = powerInteractor.detailedWakefulness.value
assertWithMessage("Cannot put device to sleep as it's already asleep!")
.that(wakefulnessModel.isAwake())
.isTrue()
powerInteractor.setAsleepForTest()
- testScope.runCurrent()
+ if (waitForLock) {
+ testScope.advanceTimeBy(
+ kosmos.userAwareSecureSettingsRepository
+ .getInt(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+ )
+ .toLong()
+ )
+ } else {
+ testScope.runCurrent()
+ }
}
/** Emulates the dismissal of the IME (soft keyboard). */
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 08b996146f2c..152911a30524 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
@@ -23,6 +23,7 @@ import android.hardware.face.FaceManager
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -59,6 +60,7 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -106,6 +108,7 @@ import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvision
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1339,7 +1342,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Putting the device to sleep to lock it again, which shouldn't report another
// successful unlock.
kosmos.powerInteractor.setAsleepForTest()
- runCurrent()
+ advanceTimeBy(
+ kosmos.userAwareSecureSettingsRepository
+ .getInt(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+ )
+ .toLong()
+ )
// Verify that the startable changed the scene to Lockscreen because the device locked
// following the sleep.
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index ea5c29ef30aa..3ad41a54ac7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -32,9 +34,9 @@ import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.compose.animation.scene.ObservableTransitionState;
@@ -42,6 +44,7 @@ import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -78,14 +81,23 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
+import java.util.List;
+
import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.test.TestScope;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class VisualStabilityCoordinatorTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return parameterizeSceneContainerFlag();
+ }
+
private VisualStabilityCoordinator mCoordinator;
@Mock private DumpManager mDumpManager;
@@ -117,6 +129,11 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
private NotificationEntry mEntry;
private GroupEntry mGroupEntry;
+ public VisualStabilityCoordinatorTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -251,6 +268,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() {
// GIVEN the panel true expanded and device isn't pulsing
setFullyDozed(false);
@@ -267,6 +285,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() {
// GIVEN the panel true expanded and device isn't pulsing
setFullyDozed(false);
@@ -520,6 +539,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
@EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
public void testNotLockscreenInGoneTransition_invalidationCalled() {
// GIVEN visual stability is being maintained b/c animation is playing
mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
@@ -589,6 +609,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
public void testCommunalShowingWillNotSuppressReordering() {
// GIVEN panel is expanded, communal is showing, and QS is collapsed
setPulsing(false);
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/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7d55169e048a..89da46544f1e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -13,11 +13,20 @@
*/
package com.android.systemui.plugins.clocks
+import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.util.DisplayMetrics
import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.internal.annotations.Keep
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.annotations.GeneratedImport
@@ -149,7 +158,7 @@ interface ClockFaceLayout {
@ProtectedReturn("return constraints;")
/** Custom constraints to apply to preview ConstraintLayout. */
- fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
+ fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet
fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
}
@@ -169,13 +178,84 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
return constraints
}
- override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
- return constraints
+ override fun applyPreviewConstraints(
+ context: Context,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ return applyDefaultPreviewConstraints(context, constraints)
}
override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
// Default clock doesn't need detailed control of view
}
+
+ companion object {
+ fun applyDefaultPreviewConstraints(
+ context: Context,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ constraints.apply {
+ val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+ constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+ val largeClockTopMargin =
+ SystemBarUtils.getStatusBarHeight(context) +
+ getDimen(context, "small_clock_padding_top") +
+ getDimen(context, "keyguard_smartspace_top_offset") +
+ getDimen(context, "date_weather_view_height") +
+ getDimen(context, "enhanced_smartspace_height")
+ connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+ connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+ // In preview, we'll show UDFPS icon for UDFPS devices
+ // and nothing for non-UDFPS devices,
+ // and we're not planning to add this vide in clockHostView
+ // so we only need position of device entry icon to constrain clock
+ // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+ val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+ val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+ connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+ val smallClockViewId = getId(context, "lockscreen_clock_view")
+ constrainWidth(smallClockViewId, WRAP_CONTENT)
+ constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+ connect(
+ smallClockViewId,
+ START,
+ PARENT_ID,
+ START,
+ getDimen(context, "clock_padding_start") +
+ getDimen(context, "status_view_margin_horizontal"),
+ )
+ val smallClockTopMargin =
+ getDimen(context, "keyguard_clock_top_margin") +
+ SystemBarUtils.getStatusBarHeight(context)
+ connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+ }
+ return constraints
+ }
+
+ fun getId(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.packageManager.getResourcesForApplication(packageName)
+ val id = res.getIdentifier(name, "id", packageName)
+ return id
+ }
+
+ fun getDimen(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.packageManager.getResourcesForApplication(packageName)
+ val id = res.getIdentifier(name, "dimen", packageName)
+ return if (id == 0) 0 else res.getDimensionPixelSize(id)
+ }
+ }
}
/** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 2a27b47e54ca..4a53df9c2f29 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,7 +24,6 @@
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
- <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
<!-- QS-->
<dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index f556b97eefc2..53d921b5e534 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -33,6 +33,9 @@
<!-- The number of columns in the infinite grid QuickSettings -->
<integer name="quick_settings_infinite_grid_num_columns">6</integer>
+ <!-- The maximum width of large tiles in the infinite grid QuickSettings -->
+ <integer name="quick_settings_infinite_grid_tile_max_width">3</integer>
+
<integer name="power_menu_lite_max_columns">2</integer>
<integer name="power_menu_lite_max_rows">3</integer>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 393631e3364b..26f32ef60851 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -126,6 +126,4 @@
<dimen name="controls_content_padding">24dp</dimen>
<dimen name="control_list_vertical_spacing">8dp</dimen>
<dimen name="control_list_horizontal_spacing">16dp</dimen>
- <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
- <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0854eb46ffdd..48af82ad7943 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -79,6 +79,9 @@
<!-- The number of columns in the infinite grid QuickSettings -->
<integer name="quick_settings_infinite_grid_num_columns">4</integer>
+ <!-- The maximum width of large tiles in the infinite grid QuickSettings -->
+ <integer name="quick_settings_infinite_grid_tile_max_width">4</integer>
+
<!-- The number of columns in the Dual Shade QuickSettings -->
<integer name="quick_settings_dual_shade_num_columns">4</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7fa287944956..67eb5b0fdf6b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -815,8 +815,7 @@
<dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
- <!-- When large clock is showing, offset the smartspace by this amount -->
- <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+
<!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
<dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
<!-- The amount to translate lockscreen elements on the GONE->AOD transition, on device fold -->
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/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 11dde6aa0dfb..71d4e9af6f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -147,7 +147,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset)
+ com.android.systemui.customization.R.dimen.keyguard_smartspace_top_offset)
* mContext.getResources().getConfiguration().fontScale
/ mContext.getResources().getDisplayMetrics().density
* SMARTSPACE_TOP_PADDING_MULTIPLIER);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 811b47d57c1d..a46b236d46fb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.app.Application;
@@ -135,6 +136,9 @@ public class SystemUIApplication extends Application implements
if (Flags.enableLayoutTracing()) {
View.setTraceLayoutSteps(true);
}
+ if (com.android.window.flags.Flags.systemUiPostAnimationEnd()) {
+ Animator.setPostNotifyEndListenerEnabled(true);
+ }
if (mProcessWrapper.isSystemUser()) {
IntentFilter bootCompletedFilter = new
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index c9c4fd594adc..6635d8b06a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt
import android.app.admin.DevicePolicyManager
import android.content.IntentFilter
import android.os.UserHandle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
@@ -57,7 +58,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
/** Defines interface for classes that can access authentication-related application state. */
@@ -178,6 +178,16 @@ interface AuthenticationRepository {
* profile of an organization-owned device.
*/
@UserIdInt suspend fun getProfileWithMinFailedUnlockAttemptsForWipe(): Int
+
+ /**
+ * Returns the device policy enforced maximum time to lock the device, in milliseconds. When the
+ * device goes to sleep, this is the maximum time the device policy allows to wait before
+ * locking the device, despite what the user setting might be set to.
+ */
+ suspend fun getMaximumTimeToLock(): Long
+
+ /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */
+ suspend fun getPowerButtonInstantlyLocks(): Boolean
}
@SysUISingleton
@@ -324,6 +334,19 @@ constructor(
}
}
+ override suspend fun getMaximumTimeToLock(): Long {
+ return withContext(backgroundDispatcher) {
+ devicePolicyManager.getMaximumTimeToLock(/* admin= */ null, selectedUserId)
+ }
+ }
+
+ /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */
+ override suspend fun getPowerButtonInstantlyLocks(): Boolean {
+ return withContext(backgroundDispatcher) {
+ lockPatternUtils.getPowerButtonInstantlyLocks(selectedUserId)
+ }
+ }
+
private val selectedUserId: Int
@UserIdInt get() = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 67579fd7f696..1c994731c393 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.authentication.domain.interactor
import android.os.UserHandle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.LockscreenCredential
@@ -49,7 +50,6 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Hosts application business logic related to user authentication.
@@ -215,7 +215,7 @@ constructor(
*/
suspend fun authenticate(
input: List<Any>,
- tryAutoConfirm: Boolean = false
+ tryAutoConfirm: Boolean = false,
): AuthenticationResult {
if (input.isEmpty()) {
throw IllegalArgumentException("Input was empty!")
@@ -254,6 +254,20 @@ constructor(
return AuthenticationResult.FAILED
}
+ /**
+ * Returns the device policy enforced maximum time to lock the device, in milliseconds. When the
+ * device goes to sleep, this is the maximum time the device policy allows to wait before
+ * locking the device, despite what the user setting might be set to.
+ */
+ suspend fun getMaximumTimeToLock(): Long {
+ return repository.getMaximumTimeToLock()
+ }
+
+ /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */
+ suspend fun getPowerButtonInstantlyLocks(): Boolean {
+ return !getAuthenticationMethod().isSecure || repository.getPowerButtonInstantlyLocks()
+ }
+
private suspend fun shouldSkipAuthenticationAttempt(
authenticationMethod: AuthenticationMethodModel,
isAutoConfirmAttempt: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index b07006887011..08b3e99fadd0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -72,7 +72,7 @@ constructor(
// Request LockSettingsService to return the Gatekeeper Password in the
// VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
// Gatekeeper Password and operationId.
- var effectiveUserId = request.userInfo.userIdForPasswordEntry
+ var effectiveUserId = request.userInfo.deviceCredentialOwnerId
val response =
if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
effectiveUserId = request.userInfo.userId
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 24278ecc76bd..b74ca035a229 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,6 +16,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.provider.Settings
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
@@ -28,20 +29,29 @@ 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.KeyguardViewMediator
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
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.distinctUntilChangedBy
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -61,6 +71,8 @@ constructor(
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
+ private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository,
+ private val keyguardInteractor: KeyguardInteractor,
) : ExclusiveActivatable() {
private val deviceUnlockSource =
@@ -176,45 +188,146 @@ constructor(
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)
+ handleLockAndUnlockEvents()
+ }
+ }
+
+ awaitCancellation()
+ }
+
+ private suspend fun handleLockAndUnlockEvents() {
+ try {
+ Log.d(TAG, "started watching for lock and unlock events")
+ coroutineScope {
+ launch { handleUnlockEvents() }
+ launch { handleLockEvents() }
+ }
+ } finally {
+ Log.d(TAG, "stopped watching for lock and unlock events")
+ }
+ }
+
+ private suspend fun handleUnlockEvents() {
+ // 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)
+ }
+ }
+
+ private suspend fun handleLockEvents() {
+ merge(
+ // Device wakefulness events.
+ powerInteractor.detailedWakefulness
+ .map { Pair(it.isAsleep(), it.lastSleepReason) }
+ .distinctUntilChangedBy { it.first }
+ .map { (isAsleep, lastSleepReason) ->
+ if (isAsleep) {
+ if (
+ lastSleepReason == WakeSleepReason.POWER_BUTTON &&
+ authenticationInteractor.getPowerButtonInstantlyLocks()
+ ) {
+ LockImmediately("locked instantly from power button")
+ } else {
+ LockWithDelay("entering sleep")
}
+ } else {
+ CancelDelayedLock("waking up")
}
-
- 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)
- }
+ },
+ // Device enters lockdown.
+ isInLockdown
+ .distinctUntilChanged()
+ .filter { it }
+ .map { LockImmediately("lockdown") },
+ // Started dreaming
+ powerInteractor.isInteractive.flatMapLatestConflated { isInteractive ->
+ // Only respond to dream state changes while the device is interactive.
+ if (isInteractive) {
+ keyguardInteractor.isDreamingAny.distinctUntilChanged().map { isDreaming ->
+ if (isDreaming) {
+ LockWithDelay("started dreaming")
+ } else {
+ CancelDelayedLock("stopped dreaming")
+ }
}
+ } else {
+ emptyFlow()
}
- } finally {
- Log.d(TAG, "stopped watching for lock and unlock events")
+ },
+ )
+ .collectLatest(::onLockEvent)
+ }
+
+ private suspend fun onLockEvent(event: LockEvent) {
+ val debugReason = event.debugReason
+ when (event) {
+ is LockImmediately -> {
+ Log.d(TAG, "locking without delay due to \"$debugReason\"")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+ }
+
+ is LockWithDelay -> {
+ val lockDelay = lockDelay()
+ Log.d(TAG, "locking in ${lockDelay}ms due to \"$debugReason\"")
+ try {
+ delay(lockDelay)
+ Log.d(
+ TAG,
+ "locking after having waited for ${lockDelay}ms due to \"$debugReason\"",
+ )
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+ } catch (_: CancellationException) {
+ Log.d(
+ TAG,
+ "delayed locking canceled, original delay was ${lockDelay}ms and reason was \"$debugReason\"",
+ )
}
}
+
+ is CancelDelayedLock -> {
+ // Do nothing, the mere receipt of this inside of a "latest" block means that any
+ // previous coroutine is automatically canceled.
+ }
}
+ }
- awaitCancellation()
+ /**
+ * Returns the amount of time to wait before locking down the device after the device has been
+ * put to sleep by the user, in milliseconds.
+ */
+ private suspend fun lockDelay(): Long {
+ val lockAfterScreenTimeoutSetting =
+ userAwareSecureSettingsRepository
+ .getInt(
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+ )
+ .toLong()
+ Log.d(TAG, "Lock after screen timeout setting set to ${lockAfterScreenTimeoutSetting}ms")
+
+ val maxTimeToLockDevicePolicy = authenticationInteractor.getMaximumTimeToLock()
+ Log.d(TAG, "Device policy max set to ${maxTimeToLockDevicePolicy}ms")
+
+ if (maxTimeToLockDevicePolicy <= 0) {
+ // No device policy enforced maximum.
+ Log.d(TAG, "No device policy max, delay is ${lockAfterScreenTimeoutSetting}ms")
+ return lockAfterScreenTimeoutSetting
+ }
+
+ val screenOffTimeoutSetting =
+ userAwareSecureSettingsRepository
+ .getInt(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ KeyguardViewMediator.KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+ )
+ .coerceAtLeast(0)
+ .toLong()
+ Log.d(TAG, "Screen off timeout setting set to ${screenOffTimeoutSetting}ms")
+
+ return (maxTimeToLockDevicePolicy - screenOffTimeoutSetting)
+ .coerceIn(minimumValue = 0, maximumValue = lockAfterScreenTimeoutSetting)
+ .also { Log.d(TAG, "Device policy max enforced, delay is ${it}ms") }
}
private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
@@ -254,6 +367,16 @@ constructor(
}
}
+ private sealed interface LockEvent {
+ val debugReason: String
+ }
+
+ private data class LockImmediately(override val debugReason: String) : LockEvent
+
+ private data class LockWithDelay(override val debugReason: String) : LockEvent
+
+ private data class CancelDelayedLock(override val debugReason: String) : LockEvent
+
companion object {
private val TAG = "DeviceUnlockedInteractor"
@VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index dd08d3262546..7a95a41770ac 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -40,7 +40,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
@@ -566,8 +565,7 @@ public class DozeTriggers implements DozeMachine.Part {
}
// When already in pulsing, we can show the new Notification without requesting a new pulse.
- if (Flags.notificationPulsingFix()
- && dozeState == State.DOZE_PULSING && reason == DozeLog.PULSE_REASON_NOTIFICATION) {
+ if (dozeState == State.DOZE_PULSING && reason == DozeLog.PULSE_REASON_NOTIFICATION) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 60a306b3e245..2ee9ddb0e453 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -239,7 +239,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
Flags.ensureKeyguardDoesTransitionStarting();
- private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
+ public static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
private static final boolean DEBUG = KeyguardConstants.DEBUG;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 3a5614fbc430..eaf8fa9585f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -114,6 +114,18 @@ interface KeyguardTransitionRepository {
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState,
)
+
+ /**
+ * Forces the current transition to emit FINISHED, foregoing any additional RUNNING steps that
+ * otherwise would have been emitted.
+ *
+ * When the screen is off, upcoming performance changes cause all Animators to cease emitting
+ * frames, which means the Animator passed to [startTransition] will never finish if it was
+ * running when the screen turned off. Also, there's simply no reason to emit RUNNING steps when
+ * the screen isn't even on. As long as we emit FINISHED, everything should end up in the
+ * correct state.
+ */
+ suspend fun forceFinishCurrentTransition()
}
@SysUISingleton
@@ -134,6 +146,7 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
private var lastStep: TransitionStep = TransitionStep()
private var lastAnimator: ValueAnimator? = null
+ private var animatorListener: AnimatorListenerAdapter? = null
private val withContextMutex = Mutex()
private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
@@ -233,7 +246,7 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
)
}
- val adapter =
+ animatorListener =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
emitTransition(
@@ -254,9 +267,10 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
animator.removeListener(this)
animator.removeUpdateListener(updateListener)
lastAnimator = null
+ animatorListener = null
}
}
- animator.addListener(adapter)
+ animator.addListener(animatorListener)
animator.addUpdateListener(updateListener)
animator.start()
return@withContext null
@@ -290,6 +304,33 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
}
}
+ override suspend fun forceFinishCurrentTransition() {
+ withContextMutex.lock()
+
+ if (lastAnimator?.isRunning != true) {
+ return
+ }
+
+ return withContext("$TAG#forceFinishCurrentTransition", mainDispatcher) {
+ withContextMutex.unlock()
+
+ Log.d(TAG, "forceFinishCurrentTransition() - emitting FINISHED early.")
+
+ lastAnimator?.apply {
+ // Cancel the animator, but remove listeners first so we don't emit CANCELED.
+ removeAllListeners()
+ cancel()
+
+ // Emit a final 1f RUNNING step to ensure that any transitions not listening for a
+ // FINISHED step end up in the right end state.
+ emitTransition(TransitionStep(currentTransitionInfo, 1f, TransitionState.RUNNING))
+
+ // Ask the listener to emit FINISHED and clean up its state.
+ animatorListener?.onAnimationEnd(this)
+ }
+ }
+ }
+
private suspend fun updateTransitionInternal(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b815f1988e7e..7cd2744cb7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,8 +19,10 @@ package com.android.systemui.keyguard.domain.interactor
import android.annotation.SuppressLint
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.keyguardTransitionForceFinishOnScreenOff
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -30,6 +32,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
@@ -59,7 +63,6 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Encapsulates business-logic related to the keyguard transitions. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -70,6 +73,7 @@ constructor(
@Application val scope: CoroutineScope,
private val repository: KeyguardTransitionRepository,
private val sceneInteractor: SceneInteractor,
+ private val powerInteractor: PowerInteractor,
) {
private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
@@ -188,6 +192,18 @@ constructor(
}
}
}
+
+ if (keyguardTransitionForceFinishOnScreenOff()) {
+ /**
+ * If the screen is turning off, finish the current transition immediately. Further
+ * frames won't be visible anyway.
+ */
+ scope.launch {
+ powerInteractor.screenPowerState
+ .filter { it == ScreenPowerState.SCREEN_TURNING_OFF }
+ .collect { repository.forceFinishCurrentTransition() }
+ }
+ }
}
fun transition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 46f5c05092eb..914fdd20e48e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -18,34 +18,23 @@
package com.android.systemui.keyguard.ui.binder
import android.content.Context
-import android.util.DisplayMetrics
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.util.Utils
import kotlin.reflect.KSuspendFunction1
/** Binder for the small clock view, large clock view. */
@@ -131,78 +120,6 @@ object KeyguardPreviewClockViewBinder {
}
}
- private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
- constraints.apply {
- constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
- // The following two lines make lockscreen_clock_view_large is constrained to available
- // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
- constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
- constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
- val largeClockTopMargin =
- SystemBarUtils.getStatusBarHeight(context) +
- context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- context.resources.getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset
- ) +
- getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
- getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
- connect(
- customR.id.lockscreen_clock_view_large,
- TOP,
- PARENT_ID,
- TOP,
- largeClockTopMargin,
- )
- connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(
- customR.id.lockscreen_clock_view_large,
- ConstraintSet.END,
- PARENT_ID,
- ConstraintSet.END,
- )
-
- // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
- // devices, but we need position of device entry icon to constrain clock
- if (getConstraint(lockId) != null) {
- connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
- } else {
- // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
- val bottomPaddingPx =
- context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
- val defaultDensity =
- DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
- DisplayMetrics.DENSITY_DEFAULT.toFloat()
- val lockIconRadiusPx = (defaultDensity * 36).toInt()
- val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
- connect(
- customR.id.lockscreen_clock_view_large,
- BOTTOM,
- PARENT_ID,
- BOTTOM,
- clockBottomMargin,
- )
- }
-
- constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
- constrainHeight(
- customR.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
- )
- connect(
- customR.id.lockscreen_clock_view,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
- )
- val smallClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
- Utils.getStatusBarHeaderHeightKeyguard(context)
- connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
- }
- }
-
private fun applyPreviewConstraints(
context: Context,
rootView: ConstraintLayout,
@@ -210,9 +127,8 @@ object KeyguardPreviewClockViewBinder {
viewModel: KeyguardPreviewClockViewModel,
) {
val cs = ConstraintSet().apply { clone(rootView) }
- applyClockDefaultConstraints(context, cs)
- previewClock.largeClock.layout.applyPreviewConstraints(cs)
- previewClock.smallClock.layout.applyPreviewConstraints(cs)
+ previewClock.largeClock.layout.applyPreviewConstraints(context, cs)
+ previewClock.smallClock.layout.applyPreviewConstraints(context, cs)
// When selectedClockSize is the initial value, make both clocks invisible to avoid
// flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ee4f41ddd5a0..6096cf74a772 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -186,12 +186,23 @@ constructor(
constraints.apply {
connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
- connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
+ connect(
+ customR.id.lockscreen_clock_view_large,
+ BOTTOM,
+ R.id.device_entry_icon_view,
+ TOP,
+ )
val largeClockTopMargin =
keyguardClockViewModel.getLargeClockTopMargin() +
getDimen(DATE_WEATHER_VIEW_HEIGHT) +
getDimen(ENHANCED_SMARTSPACE_HEIGHT)
- connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(
+ customR.id.lockscreen_clock_view_large,
+ TOP,
+ PARENT_ID,
+ TOP,
+ largeClockTopMargin,
+ )
constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
// The following two lines make lockscreen_clock_view_large is constrained to available
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/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 5c79c0b5c1bb..82adced1e1be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -181,7 +181,7 @@ constructor(
fun getLargeClockTopMargin(): Int {
return systemBarUtils.getStatusBarHeight() +
resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
- resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
}
val largeClockTopMargin: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 6579ea162ee2..65c0f57b76f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.res.R
@@ -39,20 +40,16 @@ constructor(
val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
val shouldHideSmartspace: Flow<Boolean> =
- combine(
- interactor.selectedClockSize,
- interactor.currentClockId,
- ::Pair,
- )
- .map { (size, currentClockId) ->
- when (size) {
- // TODO (b/284122375) This is temporary. We should use clockController
- // .largeClock.config.hasCustomWeatherDataDisplay instead, but
- // ClockRegistry.createCurrentClock is not reliable.
- ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
- ClockSizeSetting.SMALL -> false
- }
+ combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
+ (size, currentClockId) ->
+ when (size) {
+ // TODO (b/284122375) This is temporary. We should use clockController
+ // .largeClock.config.hasCustomWeatherDataDisplay instead, but
+ // ClockRegistry.createCurrentClock is not reliable.
+ ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+ ClockSizeSetting.SMALL -> false
}
+ }
fun getSmartspaceStartPadding(context: Context): Int {
return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
@@ -83,7 +80,7 @@ constructor(
} else {
getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
SystemBarUtils.getStatusBarHeight(context) +
- getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+ getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 850e943d17eb..ef6ae0dd6427 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,8 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -42,7 +44,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
class LockscreenContentViewModel
@AssistedInject
@@ -82,10 +83,7 @@ constructor(
unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
) { start, end ->
- UnfoldTranslations(
- start = start,
- end = end,
- )
+ UnfoldTranslations(start = start, end = end)
}
.collect { _unfoldTranslations.value = it }
}
@@ -102,17 +100,15 @@ constructor(
/** Returns a flow that indicates whether lockscreen notifications should be rendered. */
fun areNotificationsVisible(): Flow<Boolean> {
- return combine(
- clockSize,
- shadeInteractor.isShadeLayoutWide,
- ) { clockSize, isShadeLayoutWide ->
+ return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide
+ ->
clockSize == ClockSize.SMALL || isShadeLayoutWide
}
}
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
- resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+ resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) +
resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
} else {
0
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/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 8d48c1d1d23f..1cf4c23415da 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -26,11 +26,13 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -56,7 +58,7 @@ constructor(
* Unless you need to respond differently to different [WakeSleepReason]s, you should use
* [isAwake].
*/
- val detailedWakefulness = repository.wakefulness
+ val detailedWakefulness: StateFlow<WakefulnessModel> = repository.wakefulness
/**
* Whether we're awake (screen is on and responding to user touch) or asleep (screen is off, or
@@ -189,9 +191,7 @@ constructor(
* In tests, you should be able to use [setAsleepForTest] rather than calling this method
* directly.
*/
- fun onFinishedGoingToSleep(
- powerButtonLaunchGestureTriggeredDuringSleep: Boolean,
- ) {
+ fun onFinishedGoingToSleep(powerButtonLaunchGestureTriggeredDuringSleep: Boolean) {
// If the launch gesture was previously detected via onCameraLaunchGestureDetected, carry
// that state forward. It will be reset by the next onStartedGoingToSleep.
val powerButtonLaunchGestureTriggered =
@@ -255,7 +255,7 @@ constructor(
@JvmOverloads
fun PowerInteractor.setAwakeForTest(
@PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN,
- forceEmit: Boolean = false
+ forceEmit: Boolean = false,
) {
emitDuplicateWakefulnessValue = forceEmit
@@ -286,9 +286,7 @@ constructor(
emitDuplicateWakefulnessValue = forceEmit
this.onStartedGoingToSleep(reason = sleepReason)
- this.onFinishedGoingToSleep(
- powerButtonLaunchGestureTriggeredDuringSleep = false,
- )
+ this.onFinishedGoingToSleep(powerButtonLaunchGestureTriggeredDuringSleep = false)
}
/** Helper method for tests to simulate the device screen state change event. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
new file mode 100644
index 000000000000..58834037e2b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class LargeTileSpanRepository
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ @Main private val resources: Resources,
+ configurationRepository: ConfigurationRepository,
+) {
+ val span: StateFlow<Int> =
+ configurationRepository.onConfigurationChange
+ .emitOnStart()
+ .mapLatest {
+ if (resources.configuration.fontScale >= FONT_SCALE_THRESHOLD) {
+ resources.getInteger(R.integer.quick_settings_infinite_grid_tile_max_width)
+ } else {
+ 2
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 2)
+
+ private companion object {
+ const val FONT_SCALE_THRESHOLD = 2f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index ec61a0d5769e..23c79f576df5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -21,12 +21,14 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository
import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -38,6 +40,7 @@ constructor(
private val repo: DefaultLargeTilesRepository,
private val currentTilesInteractor: CurrentTilesInteractor,
private val preferencesInteractor: QSPreferencesInteractor,
+ largeTilesSpanRepo: LargeTileSpanRepository,
@PanelsLog private val logBuffer: LogBuffer,
@Application private val applicationScope: CoroutineScope,
) {
@@ -46,6 +49,8 @@ constructor(
.onEach { logChange(it) }
.stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
+ val largeTilesSpan: StateFlow<Int> = largeTilesSpanRepo.span
+
fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
fun setLargeTiles(specs: Set<TileSpec>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 74fa0fef21d7..c729c7c15176 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -37,13 +37,17 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
fun rememberEditListState(
tiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
+ largeTilesSpan: Int,
): EditTileListState {
- return remember(tiles, columns) { EditTileListState(tiles, columns) }
+ return remember(tiles, columns) { EditTileListState(tiles, columns, largeTilesSpan) }
}
/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val columns: Int) :
- DragAndDropState {
+class EditTileListState(
+ tiles: List<SizedTile<EditTileViewModel>>,
+ private val columns: Int,
+ private val largeTilesSpan: Int,
+) : DragAndDropState {
private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
override val draggedCell
get() = _draggedCell.value
@@ -86,7 +90,7 @@ class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val c
if (fromIndex != -1) {
val cell = _tiles.removeAt(fromIndex)
cell as TileGridCell
- _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) 2 else 1))
+ _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) largeTilesSpan else 1))
regenerateGrid(fromIndex)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index d10722287f5d..4a51bf06d4af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -33,7 +33,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -63,6 +62,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.size
import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
@@ -194,26 +194,35 @@ fun SmallTileContent(
is Icon.Resource -> context.getDrawable(icon.res)
}
}
- if (loadedDrawable !is Animatable) {
- Icon(icon = icon, tint = animatedColor, modifier = iconModifier)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ if (loadedDrawable is Animatable) {
val painter =
- key(icon) {
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) { atEnd = true }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ when (icon) {
+ is Icon.Resource -> {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ key(icon) {
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) { atEnd = true }
+ rememberAnimatedVectorPainter(
+ animatedImageVector = image,
+ atEnd = atEnd,
+ )
+ }
+ }
}
+ is Icon.Loaded -> rememberDrawablePainter(loadedDrawable)
}
+
Image(
painter = painter,
contentDescription = icon.contentDescription?.load(),
colorFilter = ColorFilter.tint(color = animatedColor),
modifier = iconModifier,
)
+ } else {
+ Icon(icon = icon, tint = animatedColor, modifier = iconModifier)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index b5cec120987f..31ea60e2f0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -26,7 +26,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
@@ -173,6 +173,7 @@ fun DefaultEditTileGrid(
listState: EditTileListState,
otherTiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
+ largeTilesSpan: Int,
modifier: Modifier,
onRemoveTile: (TileSpec) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
@@ -203,7 +204,7 @@ fun DefaultEditTileGrid(
containerColor = Color.Transparent,
topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
) { innerPadding ->
- CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+ CompositionLocalProvider(LocalOverscrollFactory provides null) {
val scrollState = rememberScrollState()
LaunchedEffect(listState.dragInProgress) {
if (listState.dragInProgress) {
@@ -230,7 +231,14 @@ fun DefaultEditTileGrid(
}
}
- CurrentTilesGrid(listState, selectionState, columns, onResize, onSetTiles)
+ CurrentTilesGrid(
+ listState,
+ selectionState,
+ columns,
+ largeTilesSpan,
+ onResize,
+ onSetTiles,
+ )
// Hide available tiles when dragging
AnimatedVisibility(
@@ -273,7 +281,7 @@ private fun EditGridHeader(
) {
Box(
contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
+ modifier = modifier.fillMaxWidth().wrapContentHeight(),
) {
content()
}
@@ -300,6 +308,7 @@ private fun CurrentTilesGrid(
listState: EditTileListState,
selectionState: MutableSelectionState,
columns: Int,
+ largeTilesSpan: Int,
onResize: (TileSpec, toIcon: Boolean) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
) {
@@ -340,7 +349,8 @@ private fun CurrentTilesGrid(
}
.testTag(CURRENT_TILES_GRID_TEST_TAG),
) {
- EditTiles(cells, columns, listState, selectionState, coroutineScope) { spec ->
+ EditTiles(cells, columns, listState, selectionState, coroutineScope, largeTilesSpan) { spec
+ ->
// Toggle the current size of the tile
currentListState.isIcon(spec)?.let { onResize(spec, !it) }
}
@@ -425,6 +435,7 @@ fun LazyGridScope.EditTiles(
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
coroutineScope: CoroutineScope,
+ largeTilesSpan: Int,
onToggleSize: (spec: TileSpec) -> Unit,
) {
items(
@@ -456,6 +467,7 @@ fun LazyGridScope.EditTiles(
onToggleSize = onToggleSize,
coroutineScope = coroutineScope,
bounceableInfo = cells.bounceableInfo(index, columns),
+ largeTilesSpan = largeTilesSpan,
modifier = Modifier.animateItem(),
)
}
@@ -472,6 +484,7 @@ private fun TileGridCell(
selectionState: MutableSelectionState,
onToggleSize: (spec: TileSpec) -> Unit,
coroutineScope: CoroutineScope,
+ largeTilesSpan: Int,
bounceableInfo: BounceableInfo,
modifier: Modifier = Modifier,
) {
@@ -514,8 +527,11 @@ private fun TileGridCell(
.fillMaxWidth()
.onSizeChanged {
// Grab the size before the bounceable to get the idle width
- val min = if (cell.isIcon) it.width else (it.width - padding) / 2
- val max = if (cell.isIcon) (it.width * 2) + padding else it.width
+ val totalPadding = (largeTilesSpan - 1) * padding
+ val min =
+ if (cell.isIcon) it.width else (it.width - totalPadding) / largeTilesSpan
+ val max =
+ if (cell.isIcon) (it.width * largeTilesSpan) + totalPadding else it.width
tileWidths = TileWidths(it.width, min, max)
}
.bounceable(
@@ -554,15 +570,13 @@ private fun TileGridCell(
val targetValue = if (cell.isIcon) 0f else 1f
val animatedProgress = remember { Animatable(targetValue) }
- if (selected) {
- val resizingState = selectionState.resizingState
- LaunchedEffect(targetValue, resizingState) {
- if (resizingState == null) {
- animatedProgress.animateTo(targetValue)
- } else {
- snapshotFlow { resizingState.progression }
- .collectLatest { animatedProgress.snapTo(it) }
- }
+ val resizingState = selectionState.resizingState?.takeIf { selected }
+ LaunchedEffect(targetValue, resizingState) {
+ if (resizingState == null) {
+ animatedProgress.animateTo(targetValue)
+ } else {
+ snapshotFlow { resizingState.progression }
+ .collectLatest { animatedProgress.snapTo(it) }
}
}
@@ -705,7 +719,6 @@ private fun Modifier.tileBackground(color: Color): Modifier {
private object EditModeTileDefaults {
const val PLACEHOLDER_ALPHA = .3f
- val EditGridHeaderHeight = 60.dp
val CurrentTilesGridPadding = 8.dp
@Composable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 5ac2ad02d671..29ff1715dea2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -79,7 +79,8 @@ constructor(
}
val columns = columnsWithMediaViewModel.columns
- val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
+ val largeTilesSpan by iconTilesViewModel.largeTilesSpanState
+ val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width(largeTilesSpan)) }
val bounceables =
remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
@@ -129,21 +130,23 @@ constructor(
viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking()
}
val columns = columnsViewModel.columns
+ val largeTilesSpan by iconTilesViewModel.largeTilesSpanState
val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
// Non-current tiles should always be displayed as icon tiles.
val sizedTiles =
- remember(tiles, largeTiles) {
+ remember(tiles, largeTiles, largeTilesSpan) {
tiles.map {
SizedTileImpl(
it,
- if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 else 2,
+ if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1
+ else largeTilesSpan,
)
}
}
val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
- val currentListState = rememberEditListState(currentTiles, columns)
+ val currentListState = rememberEditListState(currentTiles, columns, largeTilesSpan)
DefaultEditTileGrid(
listState = currentListState,
otherTiles = otherTiles,
@@ -154,6 +157,7 @@ constructor(
onResize = iconTilesViewModel::resize,
onStopEditing = onStopEditing,
onReset = viewModel::showResetDialog,
+ largeTilesSpan = largeTilesSpan,
)
}
@@ -171,7 +175,7 @@ constructor(
.map { it.flatten().map { it.tile } }
}
- private fun TileSpec.width(): Int {
- return if (iconTilesViewModel.isIconTile(this)) 1 else 2
+ private fun TileSpec.width(largeSize: Int = iconTilesViewModel.largeTilesSpan.value): Int {
+ return if (iconTilesViewModel.isIconTile(this)) 1 else largeSize
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 5bebdbc7a13e..9bbf290a53f0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -156,6 +156,14 @@ fun Tile(
bounceEnd = currentBounceableInfo.bounceEnd,
),
) { expandable ->
+ val longClick: (() -> Unit)? =
+ {
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+ )
+ tile.onLongClick(expandable)
+ }
+ .takeIf { uiState.handlesLongClick }
TileContainer(
onClick = {
tile.onClick(expandable)
@@ -166,12 +174,7 @@ fun Tile(
coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
}
},
- onLongClick = {
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.LONG_CLICKED
- )
- tile.onLongClick(expandable)
- },
+ onLongClick = longClick,
uiState = uiState,
iconOnly = iconOnly,
) {
@@ -192,14 +195,6 @@ fun Tile(
tile.onSecondaryClick()
}
.takeIf { uiState.handlesSecondaryClick }
- val longClick: (() -> Unit)? =
- {
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.LONG_CLICKED
- )
- tile.onLongClick(expandable)
- }
- .takeIf { uiState.handlesLongClick }
LargeTileContent(
label = uiState.label,
secondaryLabel = uiState.secondaryLabel,
@@ -237,7 +232,7 @@ private fun TileExpandable(
@Composable
fun TileContainer(
onClick: () -> Unit,
- onLongClick: () -> Unit,
+ onLongClick: (() -> Unit)?,
uiState: TileUiState,
iconOnly: Boolean,
content: @Composable BoxScope.() -> Unit,
@@ -281,7 +276,7 @@ fun Modifier.tilePadding(): Modifier {
@Composable
fun Modifier.tileCombinedClickable(
onClick: () -> Unit,
- onLongClick: () -> Unit,
+ onLongClick: (() -> Unit)?,
uiState: TileUiState,
iconOnly: Boolean,
): Modifier {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
index 9552aa935bbf..41c3de55af70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
@@ -22,7 +22,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.setValue
import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD
-class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) {
+class ResizingState(val widths: TileWidths, private val onResize: () -> Unit) {
/** Total drag offset of this resize operation. */
private var totalOffset by mutableFloatStateOf(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
index 9feaab83cc1f..a9d673aa7400 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
@@ -17,9 +17,13 @@
package com.android.systemui.qs.panels.ui.viewmodel
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.qs.panels.domain.interactor.DynamicIconTilesInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
/** View model to resize QS tiles down to icons when removed from the current tiles. */
class DynamicIconTilesViewModel
@@ -28,10 +32,21 @@ constructor(
interactorFactory: DynamicIconTilesInteractor.Factory,
iconTilesViewModel: IconTilesViewModel,
) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
+ private val hydrator = Hydrator("DynamicIconTilesViewModel")
private val interactor = interactorFactory.create()
+ val largeTilesSpanState =
+ hydrator.hydratedStateOf(
+ traceName = "largeTilesSpan",
+ source = iconTilesViewModel.largeTilesSpan,
+ )
+
override suspend fun onActivated(): Nothing {
- interactor.activate()
+ coroutineScope {
+ launch { hydrator.activate() }
+ launch { interactor.activate() }
+ awaitCancellation()
+ }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 4e698edf4e34..b8c5fbb72614 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -25,6 +25,8 @@ import kotlinx.coroutines.flow.StateFlow
interface IconTilesViewModel {
val largeTiles: StateFlow<Set<TileSpec>>
+ val largeTilesSpan: StateFlow<Int>
+
fun isIconTile(spec: TileSpec): Boolean
fun resize(spec: TileSpec, toIcon: Boolean)
@@ -34,6 +36,7 @@ interface IconTilesViewModel {
class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
IconTilesViewModel {
override val largeTiles = interactor.largeTilesSpecs
+ override val largeTilesSpan = interactor.largeTilesSpan
override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 33ce5519b68c..adc4e4bf0870 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -70,18 +70,21 @@ constructor(
source = quickQuickSettingsRowInteractor.rows,
)
+ private val largeTilesSpan by
+ hydrator.hydratedStateOf(
+ traceName = "largeTilesSpan",
+ source = iconTilesViewModel.largeTilesSpan,
+ )
+
private val currentTiles by
hydrator.hydratedStateOf(traceName = "currentTiles", source = tilesInteractor.currentTiles)
val tileViewModels by derivedStateOf {
currentTiles
- .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width()) }
.let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
}
- private val TileSpec.width: Int
- get() = if (largeTiles.contains(this)) 2 else 1
-
override suspend fun onActivated(): Nothing {
coroutineScope {
launch { hydrator.activate() }
@@ -95,4 +98,6 @@ constructor(
interface Factory {
fun create(): QuickQuickSettingsViewModel
}
+
+ private fun TileSpec.width(): Int = if (largeTiles.contains(this)) largeTilesSpan else 1
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index f218d86a5aa1..37d24debe958 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -243,15 +243,15 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
}
mSelectedCard = cards.get(selectedIndex);
switch (mSelectedCard.getCardImage().getType()) {
+ case TYPE_BITMAP:
+ case TYPE_ADAPTIVE_BITMAP:
+ mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ break;
case TYPE_URI:
case TYPE_URI_ADAPTIVE_BITMAP:
- mCardViewDrawable = null;
- break;
case TYPE_RESOURCE:
- case TYPE_BITMAP:
- case TYPE_ADAPTIVE_BITMAP:
case TYPE_DATA:
- mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+ mCardViewDrawable = null;
break;
default:
Log.e(TAG, "Unknown icon type: " + mSelectedCard.getCardImage().getType());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index f33b76b17f96..ff4760fd2837 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -18,8 +18,10 @@ package com.android.systemui.statusbar.core
import android.view.Display
import android.view.View
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.DarkIconDispatcher
@@ -46,12 +48,12 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter
import java.util.Optional
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Class responsible for managing the lifecycle and state of the status bar.
@@ -68,6 +70,7 @@ constructor(
@Assisted private val statusBarModeRepository: StatusBarModePerDisplayRepository,
@Assisted private val statusBarInitializer: StatusBarInitializer,
@Assisted private val statusBarWindowController: StatusBarWindowController,
+ @Main private val mainContext: CoroutineContext,
private val demoModeController: DemoModeController,
private val pluginDependencyProvider: PluginDependencyProvider,
private val autoHideController: AutoHideController,
@@ -141,7 +144,8 @@ constructor(
override fun start() {
StatusBarConnectedDisplays.assertInNewMode()
coroutineScope
- .launch {
+ // Perform animations on the main thread to prevent crashes.
+ .launch(context = mainContext) {
dumpManager.registerCriticalDumpable(dumpableName, this@StatusBarOrchestrator)
launch {
controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 958001625a07..1f8d365cfdad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -102,6 +102,10 @@ class NotifCollectionCache<V>(
return --lives <= 0
}
}
+
+ override fun toString(): String {
+ return "$key = $value"
+ }
}
/**
@@ -174,7 +178,10 @@ class NotifCollectionCache<V>(
pw.println("$TAG(retainCount = $retainCount, purgeTimeoutMillis = $purgeTimeoutMillis)")
pw.withIncreasedIndent {
- pw.printCollection("keys present in cache", cache.keys.stream().sorted().toList())
+ pw.printCollection(
+ "entries present in cache",
+ cache.values.stream().map { it.toString() }.sorted().toList(),
+ )
val misses = misses.get()
val hits = hits.get()
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/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index 53e6b4f82b7e..761993b3cda7 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -330,13 +330,19 @@ public class WalletScreenController implements
QAWalletCardViewInfo(Context context, WalletCard walletCard) {
mWalletCard = walletCard;
Icon cardImageIcon = mWalletCard.getCardImage();
- if (cardImageIcon.getType() == Icon.TYPE_URI) {
- mCardDrawable = null;
- } else {
+ if (cardImageIcon.getType() == Icon.TYPE_BITMAP
+ || cardImageIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+ } else {
+ mCardDrawable = null;
}
Icon icon = mWalletCard.getCardIcon();
- mIconDrawable = icon == null ? null : icon.loadDrawable(context);
+ if (icon != null && (icon.getType() == Icon.TYPE_BITMAP
+ || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
+ mIconDrawable = icon.loadDrawable(context);
+ } else {
+ mIconDrawable = null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index 1a399341f12c..ca9b866e2d18 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -283,6 +283,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
return mCardLabel;
}
+ @VisibleForTesting
+ ImageView getIcon() {
+ return mIcon;
+ }
+
@Nullable
private static Drawable getHeaderIcon(Context context, WalletCardViewInfo walletCard) {
Drawable icon = walletCard.getIcon();
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/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 96f4a60271d2..b4c69529741e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -222,7 +221,6 @@ public class DozeTriggersTest extends SysuiTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATION_PULSING_FIX)
public void testOnNotification_alreadyPulsing_notificationNotSuppressed() {
// GIVEN device is pulsing
Runnable pulseSuppressListener = mock(Runnable.class);
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/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 8a6df1cbb4de..d88d69da5e59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -63,6 +63,7 @@ class DragAndDropTest : SysuiTestCase() {
listState = listState,
otherTiles = listOf(),
columns = 4,
+ largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
onRemoveTile = {},
onSetTiles = onSetTiles,
@@ -75,7 +76,7 @@ class DragAndDropTest : SysuiTestCase() {
@Test
fun draggedTile_shouldDisappear() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) {
tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -101,7 +102,7 @@ class DragAndDropTest : SysuiTestCase() {
@Test
fun draggedTile_shouldChangePosition() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) {
tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -128,7 +129,7 @@ class DragAndDropTest : SysuiTestCase() {
@Test
fun draggedTileOut_shouldBeRemoved() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) {
tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -153,7 +154,7 @@ class DragAndDropTest : SysuiTestCase() {
@Test
fun draggedNewTileIn_shouldBeAdded() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) {
tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index d9c1d998798c..fac5ecb49027 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -62,6 +62,7 @@ class ResizingTest : SysuiTestCase() {
listState = listState,
otherTiles = listOf(),
columns = 4,
+ largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
onRemoveTile = {},
onSetTiles = {},
@@ -74,7 +75,7 @@ class ResizingTest : SysuiTestCase() {
@Test
fun toggleIconTile_shouldBeLarge() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
@@ -90,7 +91,7 @@ class ResizingTest : SysuiTestCase() {
@Test
fun toggleLargeTile_shouldBeIcon() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
@@ -106,7 +107,7 @@ class ResizingTest : SysuiTestCase() {
@Test
fun resizedLarge_shouldBeIcon() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
@@ -126,7 +127,7 @@ class ResizingTest : SysuiTestCase() {
@Test
fun resizedIcon_shouldBeLarge() {
var tiles by mutableStateOf(TestEditTiles)
- val listState = EditTileListState(tiles, 4)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index 38a61fecdc8a..21adeb01487b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -316,6 +316,31 @@ public class WalletScreenControllerTest extends SysuiTestCase {
}
@Test
+ public void queryCards_hasCards_showCarousel_invalidIconSource_noIcon() {
+ GetWalletCardsResponse response =
+ new GetWalletCardsResponse(
+ Collections.singletonList(createWalletCardWithInvalidIcon(mContext)), 0);
+
+ mController.queryWalletCards();
+ mTestableLooper.processAllMessages();
+
+ verify(mWalletClient).getWalletCards(any(), any(), mCallbackCaptor.capture());
+
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback callback =
+ mCallbackCaptor.getValue();
+
+ assertEquals(mController, callback);
+
+ callback.onWalletCardsRetrieved(response);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(VISIBLE, mWalletView.getCardCarousel().getVisibility());
+ assertEquals(GONE, mWalletView.getEmptyStateView().getVisibility());
+ assertEquals(GONE, mWalletView.getErrorView().getVisibility());
+ assertEquals(null, mWalletView.getIcon().getDrawable());
+ }
+
+ @Test
public void queryCards_noCards_showEmptyState() {
GetWalletCardsResponse response = new GetWalletCardsResponse(Collections.EMPTY_LIST, 0);
@@ -507,6 +532,16 @@ public class WalletScreenControllerTest extends SysuiTestCase {
.build();
}
+ private WalletCard createWalletCardWithInvalidIcon(Context context) {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+ return new WalletCard.Builder(
+ CARD_ID_1, createIconWithInvalidSource(), "•••• 1234", pendingIntent)
+ .setCardIcon(createIconWithInvalidSource())
+ .setCardLabel("Hold to reader")
+ .build();
+ }
+
private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
@@ -520,6 +555,10 @@ public class WalletScreenControllerTest extends SysuiTestCase {
return Icon.createWithBitmap(Bitmap.createBitmap(70, 44, Bitmap.Config.ARGB_8888));
}
+ private static Icon createIconWithInvalidSource() {
+ return Icon.createWithContentUri("content://media/external/images/media");
+ }
+
private WalletCardViewInfo createCardViewInfo(WalletCard walletCard) {
return new WalletScreenController.QAWalletCardViewInfo(
mContext, walletCard);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 48106de5225b..fc318d56a8d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2395,7 +2395,8 @@ public class BubblesTest extends SysuiTestCase {
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
assertThat(bubbleStateListener.mLastUpdate).isNotNull();
assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
BubbleBarLocation.LEFT);
@@ -2408,7 +2409,8 @@ public class BubblesTest extends SysuiTestCase {
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
}
@@ -2535,6 +2537,78 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
+ public void testEventLogging_bubbleBar_dragBarLeft() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBarRight() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBubbleLeft() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_dragBubbleRight() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+ assertBarMode();
+
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
public void testEventLogging_bubbleBar_expandAndCollapse() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 219794f3ad18..a7917a0866bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -37,9 +37,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.currentTime
-class FakeAuthenticationRepository(
- private val currentTime: () -> Long,
-) : AuthenticationRepository {
+class FakeAuthenticationRepository(private val currentTime: () -> Long) : AuthenticationRepository {
override val hintedPinLength: Int = HINTING_PIN_LENGTH
@@ -72,6 +70,9 @@ class FakeAuthenticationRepository(
private val credentialCheckingMutex = Mutex(locked = false)
+ var maximumTimeToLock: Long = 0
+ var powerButtonInstantlyLocks: Boolean = true
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return authenticationMethod.value
}
@@ -114,6 +115,7 @@ class FakeAuthenticationRepository(
MAX_FAILED_AUTH_TRIES_BEFORE_WIPE
var profileWithMinFailedUnlockAttemptsForWipe: Int = UserHandle.USER_SYSTEM
+
override suspend fun getProfileWithMinFailedUnlockAttemptsForWipe(): Int =
profileWithMinFailedUnlockAttemptsForWipe
@@ -144,10 +146,7 @@ class FakeAuthenticationRepository(
val failedAttempts = _failedAuthenticationAttempts.value
if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- AuthenticationResultModel(
- isSuccessful = isSuccessful,
- lockoutDurationMs = 0,
- )
+ AuthenticationResultModel(isSuccessful = isSuccessful, lockoutDurationMs = 0)
} else {
AuthenticationResultModel(
isSuccessful = false,
@@ -178,6 +177,14 @@ class FakeAuthenticationRepository(
credentialCheckingMutex.unlock()
}
+ override suspend fun getMaximumTimeToLock(): Long {
+ return maximumTimeToLock
+ }
+
+ override suspend fun getPowerButtonInstantlyLocks(): Boolean {
+ return powerButtonInstantlyLocks
+ }
+
private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
return when (val credentialType = getCurrentCredentialType(securityMode)) {
LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
@@ -219,9 +226,7 @@ class FakeAuthenticationRepository(
}
@LockPatternUtils.CredentialType
- private fun getCurrentCredentialType(
- securityMode: SecurityMode,
- ): Int {
+ private fun getCurrentCredentialType(securityMode: SecurityMode): Int {
return when (securityMode) {
SecurityMode.PIN,
SecurityMode.SimPin,
@@ -260,9 +265,8 @@ class FakeAuthenticationRepository(
object FakeAuthenticationRepositoryModule {
@Provides
@SysUISingleton
- fun provideFake(
- scope: TestScope,
- ) = FakeAuthenticationRepository(currentTime = { scope.currentTime })
+ fun provideFake(scope: TestScope) =
+ FakeAuthenticationRepository(currentTime = { scope.currentTime })
@Module
interface Bindings {
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 be84e3316f5b..e4c7df64fdc6 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,12 +19,14 @@ 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.keyguard.domain.interactor.keyguardInteractor
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.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
val Kosmos.deviceUnlockedInteractor by Fixture {
DeviceUnlockedInteractor(
@@ -36,6 +38,8 @@ val Kosmos.deviceUnlockedInteractor by Fixture {
powerInteractor = powerInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
+ userAwareSecureSettingsRepository = userAwareSecureSettingsRepository,
+ keyguardInteractor = keyguardInteractor,
)
.apply { activateIn(testScope) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 19e077c57de0..8209ee12ad9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -87,7 +87,7 @@ class FakeKeyguardTransitionRepository(
) : this(
initInLockscreen = true,
initiallySendTransitionStepsOnStartTransition = true,
- testScope
+ testScope,
)
private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
@@ -191,12 +191,12 @@ class FakeKeyguardTransitionRepository(
if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
sendTransitionStep(
step =
- TransitionStep(
- transitionState = TransitionState.CANCELED,
- from = lastStep.from,
- to = lastStep.to,
- value = 0f,
- )
+ TransitionStep(
+ transitionState = TransitionState.CANCELED,
+ from = lastStep.from,
+ to = lastStep.to,
+ value = 0f,
+ )
)
testScheduler.runCurrent()
}
@@ -390,6 +390,18 @@ class FakeKeyguardTransitionRepository(
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState,
) = Unit
+
+ override suspend fun forceFinishCurrentTransition() {
+ _transitions.tryEmit(
+ TransitionStep(
+ _currentTransitionInfo.value.from,
+ _currentTransitionInfo.value.to,
+ 1f,
+ TransitionState.FINISHED,
+ ownerName = _currentTransitionInfo.value.ownerName,
+ )
+ )
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index aa94c368e8f1..b9a831f11d23 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
@@ -26,6 +27,7 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- sceneInteractor = sceneInteractor
+ sceneInteractor = sceneInteractor,
+ powerInteractor = powerInteractor,
)
}
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/LargeTileSpanRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepositoryKosmos.kt
new file mode 100644
index 000000000000..a977121b3803
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.mainResources
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.largeTileSpanRepository by
+ Kosmos.Fixture {
+ LargeTileSpanRepository(applicationCoroutineScope, mainResources, configurationRepository)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index 0c62d0e85ce1..8d4db8b74061 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.largeTileSpanRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.iconTilesInteractor by
@@ -28,7 +29,8 @@ val Kosmos.iconTilesInteractor by
defaultLargeTilesRepository,
currentTilesInteractor,
qsPreferencesInteractor,
+ largeTileSpanRepository,
FakeLogBuffer.Factory.create(),
- applicationCoroutineScope
+ applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 45aab860cde7..28edae7c3689 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -49,6 +49,7 @@ val Kosmos.statusBarOrchestrator by
fakeStatusBarModePerDisplayRepository,
fakeStatusBarInitializer,
fakeStatusBarWindowController,
+ applicationCoroutineScope.coroutineContext,
mockDemoModeController,
mockPluginDependencyProvider,
mockAutoHideController,
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/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index cee29dcd1d59..7a7de3553829 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -47,6 +47,7 @@ class TestWithGoldenOutput(unittest.TestCase):
# Test to check the generated jar files to the golden output.
def test_compare_to_golden(self):
+ self.skipTest("test cannot handle multiple images (see b/378470825)")
files = os.listdir(GOLDEN_DIR)
files.sort()
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 86d3ee6d1257..d4af7b765254 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -42,9 +42,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATIN
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
+import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -55,6 +57,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -111,6 +114,8 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
@@ -338,6 +343,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private AlertDialog mEnableTouchExplorationDialog;
+ private final InputManager mInputManager;
+
private AccessibilityInputFilter mInputFilter;
private boolean mHasInputFilter;
@@ -503,6 +510,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ return AccessibilityManagerService.this.handleKeyGestureEvent(event);
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true;
+ default -> false;
+ };
+ }
+ };
+
@VisibleForTesting
AccessibilityManagerService(
Context context,
@@ -542,6 +568,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUmi = LocalServices.getService(UserManagerInternal.class);
// TODO(b/255426725): not used on tests
mVisibleBgUserIds = null;
+ mInputManager = context.getSystemService(InputManager.class);
init();
}
@@ -583,6 +610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
mUmi = LocalServices.getService(UserManagerInternal.class);
+ mInputManager = context.getSystemService(InputManager.class);
if (UserManager.isVisibleBackgroundUsersEnabled()) {
mVisibleBgUserIds = new SparseBooleanArray();
@@ -599,6 +627,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
mContext.getContentResolver());
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ }
disableAccessibilityMenuToMigrateIfNeeded();
}
@@ -640,6 +671,79 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mIsAccessibilityButtonShown;
}
+ @VisibleForTesting
+ boolean handleKeyGestureEvent(KeyGestureEvent event) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ if (!complete) {
+ return false;
+ }
+
+ String targetName;
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION:
+ targetName = MAGNIFICATION_CONTROLLER_NAME;
+ break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
+ targetName = mContext.getString(R.string.config_defaultSelectToSpeakService);
+ if (targetName.isEmpty()) {
+ return false;
+ }
+
+ final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName)
+ ? null : ComponentName.unflattenFromString(targetName);
+ AccessibilityServiceInfo accessibilityServiceInfo;
+ synchronized (mLock) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ accessibilityServiceInfo =
+ userState.getInstalledServiceInfoLocked(targetServiceComponent);
+ }
+ if (accessibilityServiceInfo == null) {
+ return false;
+ }
+
+ // Skip enabling if a warning dialog is required for the feature.
+ // TODO(b/377752960): Explore better options to instead show the warning dialog
+ // in this scenario.
+ if (isAccessibilityServiceWarningRequired(accessibilityServiceInfo)) {
+ Slog.w(LOG_TAG,
+ "Accessibility warning is required before this service can be "
+ + "activated automatically via KEY_GESTURE shortcut.");
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ List<String> shortcutTargets = getAccessibilityShortcutTargets(
+ KEY_GESTURE);
+ if (!shortcutTargets.contains(targetName)) {
+ int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ }
+ // TODO(b/377752960): Add dialog to confirm enabling the service and to
+ // activate the first time.
+ enableShortcutForTargets(true, UserShortcutType.KEY_GESTURE,
+ List.of(targetName), userId);
+
+ // Do not perform action on first press since it was just registered. Eventually,
+ // this will be a separate dialog that appears that requires the user to confirm
+ // which will resolve this race condition. For now, just require two presses the
+ // first time it is activated.
+ return true;
+ }
+
+ final int displayId = event.getDisplayId() != INVALID_DISPLAY
+ ? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId();
+ performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName);
+
+ return true;
+ }
+
@Override
public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
int windowId) {
@@ -1224,14 +1328,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
int displayId = event.getDisplayId();
final int windowId = event.getWindowId();
if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
- && displayId == Display.INVALID_DISPLAY) {
+ && displayId == INVALID_DISPLAY) {
displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
resolvedUserId, windowId);
event.setDisplayId(displayId);
}
synchronized (mLock) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && displayId != Display.INVALID_DISPLAY
+ && displayId != INVALID_DISPLAY
&& mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
shouldComputeWindows = true;
}
@@ -3257,6 +3361,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
updateAccessibilityShortcutTargetsLocked(userState, GESTURE);
updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
+ updateAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
// Update the capabilities before the mode because we will check the current mode is
// invalid or not..
updateMagnificationCapabilitiesSettingsChangeLocked(userState);
@@ -3387,6 +3492,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE);
somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE);
+ somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
@@ -3968,6 +4074,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
shortcutTypes.add(GESTURE);
}
+ shortcutTypes.add(KEY_GESTURE);
final ComponentName serviceName = service.getComponentName();
for (Integer shortcutType: shortcutTypes) {
@@ -4078,13 +4185,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
private void performAccessibilityShortcutInternal(int displayId,
@UserShortcutType int shortcutType, @Nullable String targetName) {
- final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
+ final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(
+ shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
return;
}
// In case the caller specified a target name
- if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) {
+ if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets,
+ targetName)) {
Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
targetName = null;
}
@@ -4306,6 +4415,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
+ if (shortcutType == UserShortcutType.KEY_GESTURE
+ && !enableTalkbackAndMagnifierKeyGestures()) {
+ Slog.w(LOG_TAG,
+ "KEY_GESTURE type shortcuts are disabled by feature flag");
+ return;
+ }
+
final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType);
if (shortcutType == UserShortcutType.TRIPLETAP
|| shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
@@ -5683,6 +5799,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS);
+ private final Uri mAccessibilityKeyGestureTargetsUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS);
+
private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS);
@@ -5747,6 +5866,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
contentResolver.registerContentObserver(
mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
+ mAccessibilityKeyGestureTargetsUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL);
@@ -5828,6 +5949,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilityKeyGestureTargetsUri.equals(uri)) {
+ if (readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE)) {
+ onUserStateChangedLocked(userState);
+ }
} else if (mUserNonInteractiveUiTimeoutUri.equals(uri)
|| mUserInteractiveUiTimeoutUri.equals(uri)) {
readUserRecommendedUiTimeoutSettingsLocked(userState);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 67b40632dde8..8b3e63d0dc5e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -29,6 +29,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -209,6 +210,7 @@ class AccessibilityUserState {
mShortcutTargets.put(SOFTWARE, new ArraySet<>());
mShortcutTargets.put(GESTURE, new ArraySet<>());
mShortcutTargets.put(QUICK_SETTINGS, new ArraySet<>());
+ mShortcutTargets.put(KEY_GESTURE, new ArraySet<>());
}
boolean isHandlingAccessibilityEventsLocked() {
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 23cee9db2138..1588e0421675 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@ import com.android.server.am.DropboxRateLimiter;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -400,9 +401,18 @@ public class BootReceiver extends BroadcastReceiver {
Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath());
return;
}
- // Read the proto tombstone file as bytes.
- final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+ // Read the proto tombstone file as bytes.
+ // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache
+ // via ChannelInputStream that isn't properly released. Switched to
+ // FileInputStream.transferTo() which avoids the NIO channels completely,
+ // preventing the memory leak while maintaining the same functionality.
+ final byte[] tombstoneBytes;
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ fis.transferTo(baos);
+ tombstoneBytes = baos.toByteArray();
+ }
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
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..38df10a0bc8c 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -42,6 +42,8 @@ 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.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
@@ -55,10 +57,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 +81,15 @@ 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)
+ @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 +430,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 +452,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 +479,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 +744,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 +769,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/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index f4a931f89551..d2af84cf3d30 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerService.checkComponentPermission;
import static com.android.server.am.BroadcastQueue.TAG;
-import static com.android.server.am.Flags.usePermissionManagerForBroadcastDeliveryCheck;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -289,33 +288,16 @@ public class BroadcastSkipPolicy {
if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource[] attributionSources;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSources = createAttributionSourcesForResolveInfo(info);
- } else {
- attributionSources = null;
- }
+ final AttributionSource[] attributionSources =
+ createAttributionSourcesForResolveInfo(info);
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- try {
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to " + info.activityInfo.name,
- attributionSources)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = AppGlobals.getPackageManager()
- .checkPermission(
- requiredPermission,
- info.activityInfo.applicationInfo.packageName,
- UserHandle
- .getUserId(info.activityInfo.applicationInfo.uid));
- }
- } catch (RemoteException e) {
- perm = PackageManager.PERMISSION_DENIED;
- }
+ perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to " + info.activityInfo.name,
+ attributionSources)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent + " to "
@@ -324,15 +306,6 @@ public class BroadcastSkipPolicy {
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
- if (!noteOpForManifestReceiver(appOp, r, info, component)) {
- return "Skipping delivery to " + info.activityInfo.packageName
- + " due to required appop " + appOp;
- }
- }
- }
}
}
if (r.appOp != AppOpsManager.OP_NONE) {
@@ -452,35 +425,20 @@ public class BroadcastSkipPolicy {
// Check that the receiver has the required permission(s) to receive this broadcast.
if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
- final AttributionSource attributionSource;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- attributionSource =
- new AttributionSource.Builder(filter.receiverList.uid)
- .setPid(filter.receiverList.pid)
- .setPackageName(filter.packageName)
- .setAttributionTag(filter.featureId)
- .build();
- } else {
- attributionSource = null;
- }
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(filter.receiverList.uid)
+ .setPid(filter.receiverList.pid)
+ .setPackageName(filter.packageName)
+ .setAttributionTag(filter.featureId)
+ .build();
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
- final int perm;
- if (usePermissionManagerForBroadcastDeliveryCheck()) {
- perm = hasPermissionForDataDelivery(
- requiredPermission,
- "Broadcast delivered to registered receiver " + filter.receiverId,
- attributionSource)
- ? PackageManager.PERMISSION_GRANTED
- : PackageManager.PERMISSION_DENIED;
- } else {
- perm = checkComponentPermission(
- requiredPermission,
- filter.receiverList.pid,
- filter.receiverList.uid,
- -1 /* owningUid */,
- true /* exported */);
- }
+ final int perm = hasPermissionForDataDelivery(
+ requiredPermission,
+ "Broadcast delivered to registered receiver " + filter.receiverId,
+ attributionSource)
+ ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED;
if (perm != PackageManager.PERMISSION_GRANTED) {
return "Permission Denial: receiving "
+ r.intent.toString()
@@ -491,24 +449,6 @@ public class BroadcastSkipPolicy {
+ " due to sender " + r.callerPackage
+ " (uid " + r.callingUid + ")";
}
- if (!usePermissionManagerForBroadcastDeliveryCheck()) {
- int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
- if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
- && mService.getAppOpsManager().noteOpNoThrow(appOp,
- filter.receiverList.uid, filter.packageName, filter.featureId,
- "Broadcast delivered to registered receiver " + filter.receiverId)
- != AppOpsManager.MODE_ALLOWED) {
- return "Appop Denial: receiving "
- + r.intent.toString()
- + " to " + filter.receiverList.app
- + " (pid=" + filter.receiverList.pid
- + ", uid=" + filter.receiverList.uid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(
- requiredPermission)
- + " due to sender " + r.callerPackage
- + " (uid " + r.callingUid + ")";
- }
- }
}
}
if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
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/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 09de89445122..34d4fb02ad99 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -40,6 +40,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
+import android.media.AudioDescriptor;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
@@ -47,6 +48,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManager.AudioDeviceCategory;
import android.media.AudioPort;
+import android.media.AudioProfile;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
@@ -619,6 +621,8 @@ public class AudioDeviceInventory {
final int mGroupId;
@NonNull String mPeerDeviceAddress;
@NonNull String mPeerIdentityDeviceAddress;
+ @NonNull List<AudioProfile> mAudioProfiles;
+ @NonNull List<AudioDescriptor> mAudioDescriptors;
/** Disabled operating modes for this device. Use a negative logic so that by default
* an empty list means all modes are allowed.
@@ -627,7 +631,8 @@ public class AudioDeviceInventory {
DeviceInfo(int deviceType, String deviceName, String address,
String identityAddress, int codecFormat,
- int groupId, String peerAddress, String peerIdentityAddress) {
+ int groupId, String peerAddress, String peerIdentityAddress,
+ List<AudioProfile> profiles, List<AudioDescriptor> descriptors) {
mDeviceType = deviceType;
mDeviceName = TextUtils.emptyIfNull(deviceName);
mDeviceAddress = TextUtils.emptyIfNull(address);
@@ -639,6 +644,16 @@ public class AudioDeviceInventory {
mGroupId = groupId;
mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
+ mAudioProfiles = profiles;
+ mAudioDescriptors = descriptors;
+ }
+
+ DeviceInfo(int deviceType, String deviceName, String address,
+ String identityAddress, int codecFormat,
+ int groupId, String peerAddress, String peerIdentityAddress) {
+ this(deviceType, deviceName, address, identityAddress, codecFormat,
+ groupId, peerAddress, peerIdentityAddress,
+ new ArrayList<>(), new ArrayList<>());
}
/** Constructor for all devices except A2DP sink and LE Audio */
@@ -646,6 +661,13 @@ public class AudioDeviceInventory {
this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
}
+ /** Constructor for HDMI OUT, HDMI ARC/EARC sink devices */
+ DeviceInfo(int deviceType, String deviceName, String address,
+ List<AudioProfile> profiles, List<AudioDescriptor> descriptors) {
+ this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT,
+ BluetoothLeAudio.GROUP_ID_INVALID, null, null, profiles, descriptors);
+ }
+
/** Constructor for A2DP sink devices */
DeviceInfo(int deviceType, String deviceName, String address,
String identityAddress, int codecFormat) {
@@ -1194,27 +1216,31 @@ public class AudioDeviceInventory {
}
/*package*/ void onToggleHdmi() {
- MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
- .set(MediaMetrics.Property.DEVICE,
- AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI));
+ final int[] hdmiDevices = { AudioSystem.DEVICE_OUT_HDMI,
+ AudioSystem.DEVICE_OUT_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_EARC };
+
synchronized (mDevicesLock) {
- // Is HDMI connected?
- final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
- final DeviceInfo di = mConnectedDevices.get(key);
- if (di == null) {
- Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
- mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record();
- return;
+ for (DeviceInfo di : mConnectedDevices.values()) {
+ boolean isHdmiDevice = Arrays.stream(hdmiDevices).anyMatch(device ->
+ device == di.mDeviceType);
+ if (isHdmiDevice) {
+ MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
+ .set(MediaMetrics.Property.DEVICE,
+ AudioSystem.getDeviceName(di.mDeviceType));
+ AudioDeviceAttributes ada = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.convertInternalDeviceToDeviceType(di.mDeviceType),
+ di.mDeviceAddress, di.mDeviceName, di.mAudioProfiles,
+ di.mAudioDescriptors);
+ // Toggle HDMI to retrigger broadcast with proper formats.
+ setWiredDeviceConnectionState(ada,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, "onToggleHdmi"); // disconnect
+ setWiredDeviceConnectionState(ada,
+ AudioSystem.DEVICE_STATE_AVAILABLE, "onToggleHdmi"); // reconnect
+ mmi.record();
+ }
}
- // Toggle HDMI to retrigger broadcast with proper formats.
- setWiredDeviceConnectionState(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
- AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect
- setWiredDeviceConnectionState(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
- AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect
}
- mmi.record();
}
@GuardedBy("mDevicesLock")
@@ -1818,7 +1844,15 @@ public class AudioDeviceInventory {
.printSlog(EventLogger.Event.ALOGE, TAG));
return false;
}
- mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
+
+ if (device == AudioSystem.DEVICE_OUT_HDMI ||
+ device == AudioSystem.DEVICE_OUT_HDMI_ARC ||
+ device == AudioSystem.DEVICE_OUT_HDMI_EARC) {
+ mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName,
+ address, attributes.getAudioProfiles(), attributes.getAudioDescriptors()));
+ } else {
+ mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
+ }
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
status = true;
} else if (!connect && isConnected) {
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index c0aa4cc6fa24..71f17d1f411e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -242,6 +242,11 @@ public class DisplayManagerFlags {
Flags::autoBrightnessModeBedtimeWear
);
+ private final FlagState mEnablePluginManagerFlagState = new FlagState(
+ Flags.FLAG_ENABLE_PLUGIN_MANAGER,
+ Flags::enablePluginManager
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -517,6 +522,10 @@ public class DisplayManagerFlags {
return mAutoBrightnessModeBedtimeWearFlagState.isEnabled();
}
+ public boolean isPluginManagerEnabled() {
+ return mEnablePluginManagerFlagState.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -568,6 +577,7 @@ public class DisplayManagerFlags {
pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
pw.println(" " + mHasArrSupport);
pw.println(" " + mAutoBrightnessModeBedtimeWearFlagState);
+ pw.println(" " + mEnablePluginManagerFlagState);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index a9bdccef2300..7850360c7dbf 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -446,3 +446,11 @@ flag {
bug: "365163968"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_plugin_manager"
+ namespace: "display_manager"
+ description: "Flag to enable DisplayManager plugins"
+ bug: "354059797"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 2e7f5c083ac3..6f3540221b63 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -221,6 +221,12 @@ final class InputGestureManager {
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
}
if (keyboardA11yShortcutControl()) {
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
new file mode 100644
index 000000000000..745665bab5b3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.PackageLite;
+import android.os.OutcomeReceiver;
+
+import java.util.List;
+
+/**
+ * Helper class to interact with SDK Dependency Installer service.
+ */
+public class InstallDependencyHelper {
+ private final SharedLibrariesImpl mSharedLibraries;
+
+ InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+ mSharedLibraries = sharedLibraries;
+ }
+
+ void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
+ OutcomeReceiver<Void, PackageManagerException> callback) {
+ final List<SharedLibraryInfo> missing;
+ try {
+ missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+ } catch (PackageManagerException e) {
+ callback.onError(e);
+ return;
+ }
+
+ if (missing.isEmpty()) {
+ // No need for dependency resolution. Move to installation directly.
+ callback.onResult(null);
+ return;
+ }
+
+ try {
+ bindToDependencyInstaller();
+ } catch (Exception e) {
+ PackageManagerException pe = new PackageManagerException(
+ INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
+ callback.onError(pe);
+ }
+ }
+
+ private void bindToDependencyInstaller() {
+ throw new IllegalStateException("Failed to bind to Dependency Installer");
+ }
+
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ef0997696cd7..eb70748918b6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -220,6 +220,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private AppOpsManager mAppOps;
private final VerifierController mVerifierController;
+ private final InstallDependencyHelper mInstallDependencyHelper;
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -346,6 +347,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
synchronized (mVerificationPolicyPerUser) {
mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
}
+ mInstallDependencyHelper = new InstallDependencyHelper(
+ mPm.mInjector.getSharedLibrariesImpl());
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -543,7 +546,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
session = PackageInstallerSession.readFromXml(in, mInternalCallback,
mContext, mPm, mInstallThread.getLooper(), mStagingManager,
mSessionsDir, this, mSilentUpdatePolicy,
- mVerifierController);
+ mVerifierController, mInstallDependencyHelper);
} catch (Exception e) {
Slog.e(TAG, "Could not read session", e);
continue;
@@ -1065,7 +1068,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
- mVerifierController, verificationPolicy, verificationPolicy);
+ mVerifierController, verificationPolicy, verificationPolicy,
+ mInstallDependencyHelper);
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2a92de57446d..bad12016dca7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -145,6 +145,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.PersistableBundle;
@@ -433,6 +434,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final StagingManager mStagingManager;
@NonNull private final VerifierController mVerifierController;
+ private final InstallDependencyHelper mInstallDependencyHelper;
+
final int sessionId;
final int userId;
final SessionParams params;
@@ -1188,7 +1191,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
String sessionErrorMessage, DomainSet preVerifiedDomains,
@NonNull VerifierController verifierController,
@PackageInstaller.VerificationPolicy int initialVerificationPolicy,
- @PackageInstaller.VerificationPolicy int currentVerificationPolicy) {
+ @PackageInstaller.VerificationPolicy int currentVerificationPolicy,
+ InstallDependencyHelper installDependencyHelper) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1200,6 +1204,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mVerifierController = verifierController;
mInitialVerificationPolicy = initialVerificationPolicy;
mCurrentVerificationPolicy = new AtomicInteger(currentVerificationPolicy);
+ mInstallDependencyHelper = installDependencyHelper;
this.sessionId = sessionId;
this.userId = userId;
@@ -2611,6 +2616,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
maybeFinishChildSessions(error, msg);
}
+ private void onSessionDependencyResolveFailure(int error, String msg) {
+ Slog.e(TAG, "Failed to resolve dependency for session " + sessionId);
+ // Dispatch message to remove session from PackageInstallerService.
+ dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
+ }
+
private void onSystemDataLoaderUnrecoverable() {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
@@ -3402,7 +3414,34 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/* extras= */ null, /* forPreapproval= */ false);
return;
}
- install();
+
+ if (Flags.sdkDependencyInstaller() && !isMultiPackage()) {
+ resolveLibraryDependenciesIfNeeded();
+ } else {
+ install();
+ }
+ }
+
+
+ private void resolveLibraryDependenciesIfNeeded() {
+ synchronized (mLock) {
+ // TODO(b/372862145): Callback should be called on a handler passed as parameter
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+ new OutcomeReceiver<>() {
+
+ @Override
+ public void onResult(Void result) {
+ install();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ setSessionFailed(e.error, completeMsg);
+ onSessionDependencyResolveFailure(e.error, completeMsg);
+ }
+ });
+ }
}
/**
@@ -6048,7 +6087,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@NonNull StagingManager stagingManager, @NonNull File sessionsDir,
@NonNull PackageSessionProvider sessionProvider,
@NonNull SilentUpdatePolicy silentUpdatePolicy,
- @NonNull VerifierController verifierController)
+ @NonNull VerifierController verifierController,
+ @NonNull InstallDependencyHelper installDependencyHelper)
throws IOException, XmlPullParserException {
final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -6257,6 +6297,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController,
- initialVerificationPolicy, currentVerificationPolicy);
+ initialVerificationPolicy, currentVerificationPolicy, installDependencyHelper);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index a28e3c142220..52e8c52fe6af 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -38,6 +38,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -45,7 +46,8 @@ import java.util.function.BiFunction;
/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
* used by PackageMonitor to improve the broadcast latency. */
-class PackageMonitorCallbackHelper {
+@VisibleForTesting
+public class PackageMonitorCallbackHelper {
private static final boolean DEBUG = false;
private static final String TAG = "PackageMonitorCallbackHelper";
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 929fccce5265..fc54f6864db0 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -33,6 +33,7 @@ import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.PackageLite;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -83,6 +84,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
private static final boolean DEBUG_SHARED_LIBRARIES = false;
private static final String LIBRARY_TYPE_SDK = "sdk";
+ private static final String LIBRARY_TYPE_STATIC = "static shared";
/**
* Apps targeting Android S and above need to declare dependencies to the public native
@@ -926,18 +928,19 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
if (!pkg.getUsesLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
- availablePackages, newLibraries);
+ availablePackages, newLibraries, null);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
- null, pkg.getPackageName(), "static shared", true,
- pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
+ null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
+ pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries,
+ null);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
@@ -945,13 +948,13 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
null, null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
null, null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
- newLibraries);
+ newLibraries, null);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
@@ -961,11 +964,34 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
pkg.getUsesSdkLibrariesOptional(),
pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
- usesLibraryInfos, availablePackages, newLibraries);
+ usesLibraryInfos, availablePackages, newLibraries, null);
}
return usesLibraryInfos;
}
+ List<SharedLibraryInfo> collectMissingSharedLibraryInfos(PackageLite pkgLite)
+ throws PackageManagerException {
+ ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
+ synchronized (mPm.mLock) {
+ collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
+ pkgLite.getUsesSdkLibrariesVersionsMajor(),
+ pkgLite.getUsesSdkLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+
+ collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
+ pkgLite.getUsesStaticLibrariesVersions(),
+ pkgLite.getUsesStaticLibrariesCertDigests(),
+ /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
+ /*required=*/ true, pkgLite.getTargetSdk(),
+ /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+ missingSharedLibrary);
+ }
+ return missingSharedLibrary;
+ }
+
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@@ -973,7 +999,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
- @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+ @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+ @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -986,16 +1013,33 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
libName, libVersion, mSharedLibraries, newLibraries);
}
if (libraryInfo == null) {
- // Only allow app be installed if the app specifies the sdk-library dependency is
- // optional
- if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
- && !libsOptional[i]))) {
- throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
- "Package " + packageName + " requires unavailable " + libraryType
- + " library " + libName + "; failing!");
- } else if (DEBUG_SHARED_LIBRARIES) {
- Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
- + " library " + libName + "; ignoring!");
+ if (required) {
+ boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
+ || libraryType.equals(LIBRARY_TYPE_STATIC);
+ if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
+ // TODO(b/372862145): Pass the CertDigest too
+ // If Dependency Installation is supported, try that instead of failing.
+ SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
+ libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+ );
+ outMissingSharedLibraryInfos.add(missingLibrary);
+ } else {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ }
+ } else {
+ // Only allow app be installed if the app specifies the sdk-library
+ // dependency is optional
+ boolean isOptional = libsOptional != null && libsOptional[i];
+ if (LIBRARY_TYPE_SDK.equals(libraryType) && !isOptional) {
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + packageName + " requires unavailable " + libraryType
+ + " library " + libName + "; failing!");
+ } else if (DEBUG_SHARED_LIBRARIES) {
+ Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+ + " library " + libName + "; ignoring!");
+ }
}
} else {
if (requiredVersions != null && requiredCertDigests != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index d5bea4adaf8c..b3e68a35764b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -19,6 +19,7 @@ package com.android.server.wallpaper;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.app.Flags.accurateWallpaperDownsampling;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
@@ -378,7 +379,14 @@ public class WallpaperCropper {
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
- adjustedRect.scale(1f / wallpaper.mSampleSize);
+ if (accurateWallpaperDownsampling()) {
+ adjustedRect.left = (int) (0.5f + adjustedRect.left / wallpaper.mSampleSize);
+ adjustedRect.top = (int) (0.5f + adjustedRect.top / wallpaper.mSampleSize);
+ adjustedRect.right = (int) Math.floor(adjustedRect.right / wallpaper.mSampleSize);
+ adjustedRect.bottom = (int) Math.floor(adjustedRect.bottom / wallpaper.mSampleSize);
+ } else {
+ adjustedRect.scale(1f / wallpaper.mSampleSize);
+ }
result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
}
return result;
@@ -603,6 +611,11 @@ public class WallpaperCropper {
float sampleSizeForThisOrientation = Math.max(1f, Math.min(
crop.width() / displayForThisOrientation.x,
crop.height() / displayForThisOrientation.y));
+ if (accurateWallpaperDownsampling()) {
+ sampleSizeForThisOrientation = Math.max(1f, Math.min(
+ (float) crop.width() / displayForThisOrientation.x,
+ (float) crop.height() / displayForThisOrientation.y));
+ }
sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
}
// If the total crop has more width or height than either the max texture size
@@ -746,8 +759,8 @@ public class WallpaperCropper {
final ImageDecoder.Source srcData =
ImageDecoder.createSource(wallpaper.getWallpaperFile());
final int finalScale = scale;
- final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize);
- final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize);
+ final int rescaledBitmapWidth = (int) Math.ceil(bitmapSize.x / sampleSize);
+ final int rescaledBitmapHeight = (int) Math.ceil(bitmapSize.y / sampleSize);
Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
if (!multiCrop()) decoder.setTargetSampleSize(finalScale);
if (multiCrop()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 10f096c9031b..d019516cd069 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2380,8 +2380,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
SparseArray<Rect> relativeSuggestedCrops =
mWallpaperCropper.getRelativeCropHints(wallpaper);
Point croppedBitmapSize = new Point(
- (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
- (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
+ (int) Math.ceil(wallpaper.cropHint.width() / wallpaper.mSampleSize),
+ (int) Math.ceil(wallpaper.cropHint.height() / wallpaper.mSampleSize));
if (croppedBitmapSize.equals(0, 0)) {
// There is an ImageWallpaper, but there are no crop hints and the bitmap size is
// unknown (e.g. the default wallpaper). Return a special "null" value that will be
@@ -2410,6 +2410,27 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
+ public Bundle getCurrentBitmapCrops(int which, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+ synchronized (mLock) {
+ checkPermission(READ_WALLPAPER_INTERNAL);
+ WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+ : mWallpaperMap.get(userId);
+ if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) {
+ return null;
+ }
+ Bundle bundle = new Bundle();
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ String key = String.valueOf(wallpaper.mCropHints.keyAt(i));
+ Rect rect = wallpaper.mCropHints.valueAt(i);
+ bundle.putParcelable(key, rect);
+ }
+ return bundle;
+ }
+ }
+
+ @Override
public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
int[] screenOrientations, List<Rect> crops) {
SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3f6e91590cce..9a48d5b8880d 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER;
-import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,8 +89,6 @@ public class SurfaceAnimator {
@Nullable
private Runnable mAnimationCancelledCallback;
- private boolean mAnimationStartDelayed;
-
private boolean mAnimationFinished;
/**
@@ -188,10 +185,6 @@ public class SurfaceAnimator {
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
mAnimatable.onLeashAnimationStarting(t, mLeash);
- if (mAnimationStartDelayed) {
- ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
- return;
- }
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
StringWriter sw = new StringWriter();
@@ -215,36 +208,7 @@ public class SurfaceAnimator {
null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
}
- /**
- * Begins with delaying all animations to start. Any subsequent call to {@link #startAnimation}
- * will not start the animation until {@link #endDelayingAnimationStart} is called. When an
- * animation start is being delayed, the animator is considered animating already.
- */
- void startDelayingAnimationStart() {
-
- // We only allow delaying animation start we are not currently animating
- if (!isAnimating()) {
- mAnimationStartDelayed = true;
- }
- }
-
- /**
- * See {@link #startDelayingAnimationStart}.
- */
- void endDelayingAnimationStart() {
- final boolean delayed = mAnimationStartDelayed;
- mAnimationStartDelayed = false;
- if (delayed && mAnimation != null) {
- mAnimation.startAnimation(mLeash, mAnimatable.getSyncTransaction(),
- mAnimationType, mInnerAnimationFinishedCallback);
- mAnimatable.commitPendingTransaction();
- }
- }
-
- /**
- * @return Whether we are currently running an animation, or we have a pending animation that
- * is waiting to be started with {@link #endDelayingAnimationStart}
- */
+ /** Returns whether it is currently running an animation. */
boolean isAnimating() {
return mAnimation != null;
}
@@ -290,15 +254,6 @@ public class SurfaceAnimator {
}
/**
- * Reparents the surface.
- *
- * @see #setLayer
- */
- void reparent(Transaction t, SurfaceControl newParent) {
- t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
- }
-
- /**
* @return True if the surface is attached to the leash; false otherwise.
*/
boolean hasLeash() {
@@ -319,7 +274,6 @@ public class SurfaceAnimator {
Slog.w(TAG, "Unable to transfer animation, because " + from + " animation is finished");
return;
}
- endDelayingAnimationStart();
final Transaction t = mAnimatable.getSyncTransaction();
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mLeash = from.mLeash;
@@ -336,10 +290,6 @@ public class SurfaceAnimator {
mService.mAnimationTransferMap.put(mAnimation, this);
}
- boolean isAnimationStartDelayed() {
- return mAnimationStartDelayed;
- }
-
/**
* Cancels the animation, and resets the leash.
*
@@ -361,7 +311,7 @@ public class SurfaceAnimator {
final SurfaceFreezer.Snapshot snapshot = mSnapshot;
reset(t, false);
if (animation != null) {
- if (!mAnimationStartDelayed && forwardCancel) {
+ if (forwardCancel) {
animation.onAnimationCancelled(leash);
if (animationCancelledCallback != null) {
animationCancelledCallback.run();
@@ -386,10 +336,6 @@ public class SurfaceAnimator {
mService.scheduleAnimationLocked();
}
}
-
- if (!restarting) {
- mAnimationStartDelayed = false;
- }
}
private void reset(Transaction t, boolean destroyLeash) {
@@ -495,14 +441,12 @@ public class SurfaceAnimator {
if (mLeash != null) {
mLeash.dumpDebug(proto, LEASH);
}
- proto.write(ANIMATION_START_DELAYED, mAnimationStartDelayed);
proto.end(token);
}
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
- pw.print(" mAnimationType=" + animationTypeToString(mAnimationType));
- pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
+ pw.print(" mAnimationType="); pw.println(animationTypeToString(mAnimationType));
pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
if (mAnimation != null) {
mAnimation.dump(pw, prefix + " ");
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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e0c473de0f33..5f92bb626154 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3215,8 +3215,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
&& isChangingAppTransition();
- // Delaying animation start isn't compatible with remote animations at all.
- if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
+ if (controller != null) {
// Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
boolean showBackdrop = false;
// Optionally set backdrop color if App explicitly provides it through
@@ -3639,20 +3638,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL);
}
- /**
- * @see SurfaceAnimator#startDelayingAnimationStart
- */
- void startDelayingAnimationStart() {
- mSurfaceAnimator.startDelayingAnimationStart();
- }
-
- /**
- * @see SurfaceAnimator#endDelayingAnimationStart
- */
- void endDelayingAnimationStart() {
- mSurfaceAnimator.endDelayingAnimationStart();
- }
-
@Override
public int getSurfaceWidth() {
return mSurfaceControl.getWidth();
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/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index cc5573bb01d8..f34ec72d7e27 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -19,6 +19,7 @@ package com.android.server.policy;
import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
@@ -71,6 +72,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private static final int DEVICE_STATE_OPENED = 2;
private static final int DEVICE_STATE_REAR_DISPLAY = 3;
private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+ private static final int DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT = 5;
private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -130,14 +132,17 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
&& hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
}),
- createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
- ALLOWED),
- createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
- NOT_ALLOWED),
- createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
- NOT_ALLOWED, /* availabilityPredicate= */
- provider -> !mIsDualDisplayBlockingEnabled
- || provider.hasNoConnectedExternalDisplay())};
+ createConfig(getOpenedDeviceState(),
+ /* activeStatePredicate= */ ALLOWED),
+ createConfig(getRearDisplayDeviceState(),
+ /* activeStatePredicate= */ NOT_ALLOWED),
+ createConfig(getDualDisplayDeviceState(),
+ /* activeStatePredicate= */ NOT_ALLOWED,
+ /* availabilityPredicate= */ provider -> !mIsDualDisplayBlockingEnabled
+ || provider.hasNoConnectedExternalDisplay()),
+ createConfig(getRearDisplayOuterDefaultState(),
+ /* activeStatePredicate= */ NOT_ALLOWED)
+ };
}
private DeviceStatePredicateWrapper createClosedConfiguration(
@@ -266,4 +271,24 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
.setSystemProperties(systemProperties)
.build());
}
+
+ /**
+ * Returns the {link DeviceState.Configuration} that represents the new rear display state
+ * where the inner display is also enabled, showing a system affordance to exit the state.
+ */
+ @NonNull
+ private DeviceState getRearDisplayOuterDefaultState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_EMULATED_ONLY,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+ PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(
+ DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT,
+ "REAR_DISPLAY_OUTER_DEFAULT")
+ .setSystemProperties(systemProperties)
+ .build());
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9759772ae8bd..19b03437292f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1621,7 +1621,8 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
t.traceEnd();
- if (!isWatch && android.app.supervision.flags.Flags.supervisionApi()) {
+ if (android.app.supervision.flags.Flags.supervisionApi()
+ && (!isWatch || android.app.supervision.flags.Flags.supervisionApiOnWear())) {
t.traceBegin("StartSupervisionService");
mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
new file mode 100644
index 000000000000..1be5cef28244
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.Flags.FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeNonSdkSandbox;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@AppModeFull
+@AppModeNonSdkSandbox
+@RunWith(AndroidJUnit4.class)
+public class BroadcastHelperTest {
+ private static final String TAG = "BroadcastHelperTest";
+ private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
+ private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
+ PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
+ private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+ "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Mock
+ ActivityManagerInternal mMockActivityManagerInternal;
+ @Mock
+ AndroidPackageInternal mMockAndroidPackageInternal;
+ @Mock
+ Computer mMockSnapshot;
+ @Mock
+ Handler mMockHandler;
+ @Mock
+ PackageManagerServiceInjector mMockPackageManagerServiceInjector;
+ @Mock
+ PackageMonitorCallbackHelper mMockPackageMonitorCallbackHelper;
+ @Mock
+ PackageStateInternal mMockPackageStateInternal;
+ @Mock
+ ParsedActivity mMockParsedActivity;
+ @Mock
+ UserManagerInternal mMockUserManagerInternal;
+
+ private Context mContext;
+ private BroadcastHelper mBroadcastHelper;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ when(mMockHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+ i -> {
+ ((Message) i.getArguments()[0]).getCallback().run();
+ return true;
+ });
+ when(mMockPackageManagerServiceInjector.getActivityManagerInternal()).thenReturn(
+ mMockActivityManagerInternal);
+ when(mMockPackageManagerServiceInjector.getContext()).thenReturn(mContext);
+ when(mMockPackageManagerServiceInjector.getHandler()).thenReturn(mMockHandler);
+ when(mMockPackageManagerServiceInjector.getPackageMonitorCallbackHelper()).thenReturn(
+ mMockPackageMonitorCallbackHelper);
+ when(mMockPackageManagerServiceInjector.getUserManagerInternal()).thenReturn(
+ mMockUserManagerInternal);
+
+ mBroadcastHelper = new BroadcastHelper(mMockPackageManagerServiceInjector);
+ }
+
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
+ captor.capture(), eq(null),
+ eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
+ anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertThat(intent.getPackage()).isEqualTo("android");
+ }
+
+ @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+ @Test
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
+ throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+ eq(null), anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
+ changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */);
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+ eq(null), anyInt(), eq(null), eq(null), eq(null));
+ Intent intent = captor.getValue();
+ assertNotNull(intent);
+ assertNull(intent.getPackage());
+ }
+
+ private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) {
+ when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+ anyInt())).thenReturn(mMockPackageStateInternal);
+ when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
+ when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+ when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
+
+ when(mMockParsedActivity.getClassName()).thenReturn(
+ PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+ when(mMockParsedActivity.isExported()).thenReturn(changeExportedComponent);
+ ArrayList<ParsedActivity> parsedActivities = new ArrayList<>();
+ parsedActivities.add(mMockParsedActivity);
+
+ when(mMockAndroidPackageInternal.getReceivers()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getProviders()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getServices()).thenReturn(new ArrayList<>());
+ when(mMockAndroidPackageInternal.getActivities()).thenReturn(parsedActivities);
+
+ doNothing().when(mMockPackageMonitorCallbackHelper).notifyPackageChanged(any(),
+ anyBoolean(), any(), anyInt(), any(), any(), any(), any(), any());
+ when(mMockActivityManagerInternal.broadcastIntentWithCallback(any(), any(), any(), anyInt(),
+ any(), any(), any())).thenReturn(ActivityManager.BROADCAST_SUCCESS);
+
+ ArrayList<String> componentNames = new ArrayList<>();
+ componentNames.add(PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+
+ mBroadcastHelper.sendPackageChangedBroadcast(mMockSnapshot,
+ PACKAGE_CHANGED_TEST_PACKAGE_NAME, true /* dontKillApp */, componentNames,
+ UserHandle.USER_SYSTEM, "test" /* reason */);
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 09d0e4a82f7f..5a59c57ddf28 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -201,7 +201,8 @@ class PackageInstallerSessionTest {
/* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
/* VerifierController */ mock(VerifierController::class.java),
/* initialVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
- /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
+ /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+ /* installDependencyHelper */ null
)
}
@@ -256,7 +257,8 @@ class PackageInstallerSessionTest {
mTmpDir,
mock(PackageSessionProvider::class.java),
mock(SilentUpdatePolicy::class.java),
- mock(VerifierController::class.java)
+ mock(VerifierController::class.java),
+ mock(InstallDependencyHelper::class.java)
)
ret.add(session)
} catch (e: Exception) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index be698b2673ad..6acf2421ba75 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -109,6 +109,10 @@ android_test {
optimize: {
enabled: false,
},
+
+ data: [
+ ":HelloWorldUsingSdk1And2",
+ ],
}
java_library {
@@ -134,6 +138,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/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index 7782d570856f..2b90119145bd 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -23,6 +23,12 @@
<option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true"/>
+ <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+ value="/data/local/tmp/tests/smockingservicestest/pm/HelloWorldUsingSdk1And2.apk"/>
+ </target_preparer>
+
<option name="test-tag" value="FrameworksMockingServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
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..9d92d5fe4f60 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,10 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
/**
* Verify that when dispatching we respect tranches of priority.
*/
+ @DisableFlags(Flags.FLAG_LIMIT_PRIORITY_SCOPE)
+ @SuppressWarnings("DistinctVarargsChecker")
@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 +1599,106 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
}
/**
+ * Verify that when dispatching we respect tranches of priority.
+ */
+ @SuppressWarnings("DistinctVarargsChecker")
+ @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)
+ @SuppressWarnings("DistinctVarargsChecker")
+ @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 +2410,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 +2451,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/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
new file mode 100644
index 000000000000..f6c644e3d4d4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.pm;
+
+import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.OutcomeReceiver;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(JUnit4.class)
+@RequiresFlagsEnabled(FLAG_SDK_DEPENDENCY_INSTALLER)
+public class InstallDependencyHelperTest {
+
+ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Rule public final CheckFlagsRule checkFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
+ private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+ @Mock private SharedLibrariesImpl mSharedLibraries;
+ private InstallDependencyHelper mInstallDependencyHelper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
+ throws Exception {
+ doThrow(new PackageManagerException(new Exception("xyz")))
+ .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
+
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains("xyz");
+ }
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
+ // Return a non-empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.singletonList(
+ mock(SharedLibraryInfo.class));
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertFailure();
+
+ assertThat(callback.error).hasMessageThat().contains(
+ "Failed to bind to Dependency Installer");
+ }
+
+
+ @Test
+ public void testResolveLibraryDependenciesIfNeeded_allDependenciesInstalled() throws Exception {
+ // Return an empty list as missing dependency
+ PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+ List<SharedLibraryInfo> missingDependency = Collections.emptyList();
+ when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+ .thenReturn(missingDependency);
+
+ CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
+ mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+ callback.assertSuccess();
+ }
+
+ private static class CallbackHelper implements OutcomeReceiver<Void, PackageManagerException> {
+ public PackageManagerException error;
+
+ private final CountDownLatch mWait = new CountDownLatch(1);
+ private final boolean mExpectSuccess;
+
+ CallbackHelper(boolean expectSuccess) {
+ mExpectSuccess = expectSuccess;
+ }
+
+ @Override
+ public void onResult(Void result) {
+ if (!mExpectSuccess) {
+ fail("Expected to fail");
+ }
+ mWait.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull PackageManagerException e) {
+ if (mExpectSuccess) {
+ fail("Expected success but received: " + e);
+ }
+ error = e;
+ mWait.countDown();
+ }
+
+ void assertSuccess() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNull();
+ }
+
+ void assertFailure() throws Exception {
+ assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(error).isNotNull();
+ }
+
+ }
+
+ private PackageLite getPackageLite(String apkFileName) throws Exception {
+ File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+ ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+ ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+ assertThat(result.isError()).isFalse();
+ ApkLite baseApk = result.getResult();
+
+ return new PackageLite(/*path=*/ null, baseApk.getPath(), baseApk,
+ /*splitNames=*/ null, /*isFeatureSplits=*/ null, /*usesSplitNames=*/ null,
+ /*configForSplit=*/ null, /*splitApkPaths=*/ null,
+ /*splitRevisionCodes=*/ null, baseApk.getTargetSdkVersion(),
+ /*requiredSplitTypes=*/ null, /*splitTypes=*/ null);
+ }
+
+ private File copyApkToTmpDir(String apkFileName) throws Exception {
+ File outFile = temporaryFolder.newFile(apkFileName);
+ String apkFilePath = PUSH_FILE_DIR + apkFileName;
+ File apkFile = new File(apkFilePath);
+ assertThat(apkFile.exists()).isTrue();
+ try (InputStream is = new FileInputStream(apkFile)) {
+ FileUtils.copyToFileOrThrow(is, outFile);
+ }
+ return outFile;
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 591e8df1725b..71c60ad02794 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -742,10 +742,11 @@ public class StagingManagerTest {
/* stagedSessionErrorMessage */ "no error",
/* preVerifiedDomains */ null,
/* verifierController */ null,
- /* initialVerificationPolicy */
+ /* initialVerificationPolicy */
PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
/* currentVerificationPolicy */
- PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED);
+ PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+ /* installDependencyHelper */ null);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 2edde9b74d0a..d5b930769e43 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -33,6 +33,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
@@ -80,6 +81,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -2183,6 +2185,168 @@ public class AccessibilityManagerServiceTest {
verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class);
}
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_toggleMagnifier() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(MAGNIFICATION_CONTROLLER_NAME);
+
+ // The magnifier will only be toggled on the second event received since the first is
+ // used to toggle the feature on.
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ verify(mInputFilter).notifyMagnificationShortcutTriggered(anyInt());
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_trustedService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo trustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(trustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ trustedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{trustedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).containsExactly(
+ trustedService.getComponentName().flattenToString());
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_preinstalledService() {
+ setupAccessibilityServiceConnection(FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo untrustedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(untrustedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ untrustedService.getComponentName().flattenToString());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_downloadedService() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo downloadedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ false, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(downloadedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ downloadedService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{downloadedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_defaultNotInstalled() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ final AccessibilityServiceInfo defaultService = mockAccessibilityServiceInfo(
+ new ComponentName("package_b", "class_b"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.string.config_defaultSelectToSpeakService,
+ defaultService.getComponentName().flattenToString());
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{defaultService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
+ public void handleKeyGestureEvent_activateSelectToSpeak_noDefault() {
+ mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
+
+ final AccessibilityServiceInfo installedService = mockAccessibilityServiceInfo(
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ true);
+ AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mInstalledServices.add(installedService);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_trustedAccessibilityServices,
+ new String[]{installedService.getComponentName().flattenToString()});
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+
+ mA11yms.handleKeyGestureEvent(new KeyGestureEvent.Builder().setKeyGestureType(
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK).setAction(
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+
+ assertThat(ShortcutUtils.getShortcutTargetsFromSettings(mTestableContext, KEY_GESTURE,
+ mA11yms.getCurrentUserIdLocked())).isEmpty();
+ }
private Set<String> readStringsFromSetting(String setting) {
final Set<String> result = new ArraySet<>();
@@ -2298,6 +2462,10 @@ public class AccessibilityManagerServiceTest {
AccessibilityManagerService service) {
super(context, service);
}
+
+ @Override
+ void notifyMagnificationShortcutTriggered(int displayId) {
+ }
}
private static class A11yTestableContext extends TestableContext {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 8c35925debff..cb52eef6adfe 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -32,6 +32,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP;
@@ -174,6 +175,7 @@ public class AccessibilityUserStateTest {
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), SOFTWARE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), GESTURE);
mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), QUICK_SETTINGS);
+ mUserState.updateShortcutTargetsLocked(Set.of(componentNameString), KEY_GESTURE);
mUserState.updateA11yTilesInQsPanelLocked(
Set.of(AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME));
mUserState.setTargetAssignedToAccessibilityButton(componentNameString);
@@ -201,6 +203,7 @@ public class AccessibilityUserStateTest {
assertTrue(mUserState.getShortcutTargetsLocked(SOFTWARE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(GESTURE).isEmpty());
assertTrue(mUserState.getShortcutTargetsLocked(QUICK_SETTINGS).isEmpty());
+ assertTrue(mUserState.getShortcutTargetsLocked(KEY_GESTURE).isEmpty());
assertTrue(mUserState.getA11yQsTilesInQsPanel().isEmpty());
assertNull(mUserState.getTargetAssignedToAccessibilityButton());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 411a6102f45a..361df94e8a90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -44,7 +44,7 @@ import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.server.UiServiceTestCase;
@@ -89,7 +89,7 @@ import java.util.stream.Stream;
import javax.annotation.Nullable;
@RunWith(AndroidJUnit4.class)
-@EnableFlags({Flags.FLAG_VISIT_PERSON_URI, Flags.FLAG_API_RICH_ONGOING})
+@EnableFlags({Flags.FLAG_API_RICH_ONGOING})
public class NotificationVisitUrisTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 9967ccebeb1f..7dba1422d61d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -21,7 +21,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static org.junit.Assert.assertEquals;
@@ -165,31 +164,6 @@ public class SurfaceAnimatorTest extends WindowTestsBase {
}
@Test
- public void testDelayingAnimationStart() {
- mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
- mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- verifyZeroInteractions(mSpec);
- assertAnimating(mAnimatable);
- assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed());
- mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
- verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), any());
- }
-
- @Test
- public void testDelayingAnimationStartAndCancelled() {
- mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
- mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
- ANIMATION_TYPE_APP_TRANSITION);
- mAnimatable.mSurfaceAnimator.cancelAnimation();
- verifyZeroInteractions(mSpec);
- assertNotAnimating(mAnimatable);
- assertTrue(mAnimatable.mFinishedCallbackCalled);
- assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
- verify(mTransaction).remove(eq(mAnimatable.mLeash));
- }
-
- @Test
public void testTransferAnimation() {
mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
ANIMATION_TYPE_APP_TRANSITION);
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 88dddcfc9180..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
*/
@@ -2332,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.
*
@@ -2436,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>
*
@@ -2759,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>
*
@@ -2830,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>
*
@@ -3147,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 544bfabcf20b..210200be4cf3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -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 9a9a331a3753..ea61ad9d4481 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
@@ -28,6 +28,7 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
import android.view.WindowInsets
import android.view.WindowManager
+import android.window.DesktopModeFlags
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
@@ -35,7 +36,6 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH
-import com.android.window.flags.Flags
import java.time.Duration
/**
@@ -107,7 +107,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
// drag the window to move to desktop
if (motionEventHelper.inputMethod == TOUCH
- && Flags.enableHoldToDragAppHandle()) {
+ && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) {
// Touch requires hold-to-drag.
motionEventHelper.holdToDrag(startX, startY, startX, endY, steps = 100)
} else {
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 61400edba165..09a686ca2c3f 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -782,6 +782,30 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + M -> Toggle Magnification",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_M
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + S -> Activate Select to Speak",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_S
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index e2099e652c49..635e5de935c7 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -18,19 +18,27 @@ package com.android.server.usblib;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
import android.os.Binder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -43,13 +51,36 @@ public class UsbManagerTestLib {
private UsbManager mUsbManagerSys;
private UsbManager mUsbManagerMock;
- @Mock private android.hardware.usb.IUsbManager mMockUsbService;
+ @Mock
+ private android.hardware.usb.IUsbManager mMockUsbService;
+ private TestParcelFileDescriptor mTestParcelFileDescriptor = new TestParcelFileDescriptor(
+ new ParcelFileDescriptor(new FileDescriptor()));
+ @Mock
+ private UsbAccessory mMockUsbAccessory;
/**
* Counter for tracking UsbOperation operations.
*/
private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+ private class TestParcelFileDescriptor extends ParcelFileDescriptor {
+
+ private final AtomicInteger mCloseCount = new AtomicInteger();
+
+ TestParcelFileDescriptor(ParcelFileDescriptor wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public void close() {
+ int unused = mCloseCount.incrementAndGet();
+ }
+
+ public void clearCloseCount() {
+ mCloseCount.set(0);
+ }
+ }
+
public UsbManagerTestLib(Context context) {
MockitoAnnotations.initMocks(this);
mContext = context;
@@ -74,6 +105,34 @@ public class UsbManagerTestLib {
mUsbManagerSys.setCurrentFunctions(functions);
}
+ private InputStream openAccessoryInputStream(UsbAccessory accessory) {
+ try {
+ when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+ } catch (RemoteException remEx) {
+ Log.w(TAG, "RemoteException");
+ }
+
+ if (Flags.enableAccessoryStreamApi()) {
+ return mUsbManagerMock.openAccessoryInputStream(accessory);
+ }
+
+ throw new UnsupportedOperationException("Stream APIs not available");
+ }
+
+ private OutputStream openAccessoryOutputStream(UsbAccessory accessory) {
+ try {
+ when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+ } catch (RemoteException remEx) {
+ Log.w(TAG, "RemoteException");
+ }
+
+ if (Flags.enableAccessoryStreamApi()) {
+ return mUsbManagerMock.openAccessoryOutputStream(accessory);
+ }
+
+ throw new UnsupportedOperationException("Stream APIs not available");
+ }
+
private void testSetGetCurrentFunctions_Matched(long functions) {
setCurrentFunctions(functions);
assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions());
@@ -94,7 +153,7 @@ public class UsbManagerTestLib {
try {
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
} catch (RemoteException remEx) {
Log.w(TAG, "RemoteException");
}
@@ -118,7 +177,7 @@ public class UsbManagerTestLib {
int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
}
public void testGetCurrentFunctions_shouldMatched() {
@@ -138,4 +197,47 @@ public class UsbManagerTestLib {
testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS);
testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM);
}
+
+ public void testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed() {
+ mTestParcelFileDescriptor.clearCloseCount();
+ try {
+ try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+ //noinspection EmptyTryBlock
+ try (OutputStream ignored2 = openAccessoryOutputStream(mMockUsbAccessory)) {
+ // do nothing
+ }
+ }
+
+ // ParcelFileDescriptor is closed only once.
+ assertEquals(mTestParcelFileDescriptor.mCloseCount.get(), 1);
+ mTestParcelFileDescriptor.clearCloseCount();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ public void testOnlyOneOpenInputStreamAllowed() {
+ try {
+ //noinspection EmptyTryBlock
+ try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+ assertThrows(IllegalStateException.class,
+ () -> openAccessoryInputStream(mMockUsbAccessory));
+ }
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ public void testOnlyOneOpenOutputStreamAllowed() {
+ try {
+ //noinspection EmptyTryBlock
+ try (OutputStream ignored = openAccessoryOutputStream(mMockUsbAccessory)) {
+ assertThrows(IllegalStateException.class,
+ () -> openAccessoryOutputStream(mMockUsbAccessory));
+ }
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
}
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
index 8b21763b4a24..40fd0b431451 100644
--- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
@@ -18,17 +18,21 @@ package com.android.server.usbtest;
import android.content.Context;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Ignore;
+import com.android.server.usblib.UsbManagerTestLib;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import com.android.server.usblib.UsbManagerTestLib;
-
/**
* Unit tests for {@link android.hardware.usb.UsbManager}.
* Note: MUST claimed MANAGE_USB permission in Manifest
@@ -41,6 +45,9 @@ public class UsbManagerApiTest {
private final UsbManagerTestLib mUsbManagerTestLib =
new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
/**
* Verify NO SecurityException
* Go through System Server
@@ -92,4 +99,23 @@ public class UsbManagerApiTest {
public void testUsbApi_SetCurrentFunctions_shouldMatched() {
mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_closesParcelFileDescriptorAfterAllStreamsClosed() {
+ mUsbManagerTestLib.testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_callingOpenAccessoryInputStreamTwiceThrowsException() {
+ mUsbManagerTestLib.testOnlyOneOpenInputStreamAllowed();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+ public void testUsbApi_callingOpenAccessoryOutputStreamTwiceThrowsException() {
+ mUsbManagerTestLib.testOnlyOneOpenOutputStreamAllowed();
+ }
+
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ac96ef28f501..be5c84c0353c 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,7 +53,6 @@ public class TestableLooper {
private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
private static final Field MESSAGE_NEXT_FIELD;
private static final Field MESSAGE_WHEN_FIELD;
- private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
private Looper mLooper;
private MessageQueue mQueue;
@@ -64,14 +63,6 @@ public class TestableLooper {
static {
try {
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
- MessageQueue.class.getDeclaredField("mUseConcurrent");
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
- } catch (NoSuchFieldException ignored) {
- // Ignore - maybe this is not CombinedMessageQueue?
- }
-
- try {
MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -155,15 +146,6 @@ public class TestableLooper {
mLooper = l;
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
-
- // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
- if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
- try {
- MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- }
}
/**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 1bcfaf60857d..56b0a25ed2dd 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -100,18 +100,6 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
- // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
- try {
- Field messageQueueUseConcurrentField =
- MessageQueue.class.getDeclaredField("mUseConcurrent");
- messageQueueUseConcurrentField.setAccessible(true);
- messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
- } catch (NoSuchFieldException e) {
- // Ignore - maybe this is not CombinedMessageQueue?
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Reflection error constructing or accessing looper", e);
- }
-
mClock = clock;
}