summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java157
-rw-r--r--core/api/current.txt5
-rw-r--r--core/api/system-current.txt13
-rw-r--r--core/java/Android.bp27
-rw-r--r--core/java/android/app/ActivityManager.java4
-rw-r--r--core/java/android/app/ActivityManagerInternal.java3
-rw-r--r--core/java/android/app/ActivityThread.java18
-rw-r--r--core/java/android/app/LoadedApk.java2
-rw-r--r--core/java/android/app/OWNERS1
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java34
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java74
-rw-r--r--core/java/android/app/usage/UsageEventsQuery.java2
-rw-r--r--core/java/android/companion/AssociationInfo.java2
-rw-r--r--core/java/android/companion/AssociationRequest.java4
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java19
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/content/Intent.java45
-rw-r--r--core/java/android/content/pm/IPackageInstaller.aidl6
-rw-r--r--core/java/android/content/pm/PackageInstaller.java135
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig12
-rw-r--r--core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl2
-rw-r--r--core/java/android/content/pm/verify/pkg/VerificationSession.java54
-rw-r--r--core/java/android/os/BaseBundle.java4
-rw-r--r--core/java/android/os/Bundle.java11
-rw-r--r--core/java/android/os/Debug.java19
-rw-r--r--core/java/android/os/Process.java2
-rw-r--r--core/java/android/security/forensic/IForensicService.aidl32
-rw-r--r--core/java/android/security/forensic/IForensicServiceCommandCallback.aidl33
-rw-r--r--core/java/android/security/forensic/IForensicServiceStateCallback.aidl31
-rw-r--r--core/java/android/util/NtpTrustedTime.java18
-rw-r--r--core/java/android/view/ViewRootImpl.java29
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig10
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java1
-rw-r--r--core/res/AndroidManifest.xml12
-rw-r--r--core/res/res/values/themes_device_defaults.xml176
-rw-r--r--core/tests/coretests/src/android/app/OWNERS2
-rw-r--r--core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java47
-rw-r--r--core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java6
-rw-r--r--core/tests/coretests/src/android/os/OWNERS3
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java38
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--errorprone/OWNERS3
-rw-r--r--graphics/java/android/graphics/text/PositionedGlyphs.java39
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt34
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt111
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt94
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java22
-rw-r--r--libs/appfunctions/api/current.txt5
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java27
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java71
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp97
-rw-r--r--media/java/android/media/IMediaRouterService.aidl3
-rw-r--r--media/java/android/media/MediaRouter2.java14
-rw-r--r--media/java/android/media/MediaRouter2Manager.java10
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml4
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml13
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml13
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml176
-rw-r--r--packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt31
-rw-r--r--packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt104
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt166
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt8
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt)0
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt)0
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt84
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt91
-rw-r--r--packages/SettingsLib/res/values/strings.xml5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java248
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java16
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt126
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt48
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt47
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt10
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt56
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt6
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt6
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt5
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt6
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt)59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt101
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt)8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt)8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt)47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt140
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt2
-rw-r--r--packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml5
-rw-r--r--packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml17
-rw-r--r--packages/SystemUI/res/layout/hearing_tool_item.xml10
-rw-r--r--packages/SystemUI/res/values/config.xml21
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt179
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java)30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java)27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarScope.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentScope.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt)24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/RetroText.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt195
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt)45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt49
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt)6
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java17
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java3
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java3
-rw-r--r--ravenwood/tests/bivalenttest/Android.bp62
-rw-r--r--services/Android.bp16
-rw-r--r--services/accessibility/accessibility.aconfig15
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java3
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java3
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java32
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java4
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java40
-rw-r--r--services/core/java/com/android/server/SystemConfig.java52
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java76
-rw-r--r--services/core/java/com/android/server/am/BroadcastController.java1
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java7
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java8
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java1
-rw-r--r--services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java477
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java17
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java12
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java14
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java95
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java40
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java177
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java25
-rw-r--r--services/core/java/com/android/server/pm/verify/pkg/VerifierController.java58
-rw-r--r--services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java3
-rw-r--r--services/core/java/com/android/server/security/forensic/ForensicService.java294
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java114
-rw-r--r--services/core/java/com/android/server/uri/NeededUriGrants.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java32
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java159
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java2
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java6
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java4
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java3
-rw-r--r--services/core/java/com/android/server/wm/Task.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java7
-rw-r--r--services/java/com/android/server/SystemServer.java27
-rw-r--r--services/people/java/com/android/server/people/data/CallLogQueryHelper.java2
-rw-r--r--services/people/java/com/android/server/people/data/ContactsQueryHelper.java8
-rw-r--r--services/people/java/com/android/server/people/data/MmsQueryHelper.java4
-rw-r--r--services/people/java/com/android/server/people/data/SmsQueryHelper.java2
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt15
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java106
-rw-r--r--services/tests/appfunctions/Android.bp2
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt89
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java49
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java686
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java3
-rw-r--r--services/tests/security/forensic/Android.bp41
-rw-r--r--services/tests/security/forensic/AndroidManifest.xml27
-rw-r--r--services/tests/security/forensic/AndroidTest.xml32
-rw-r--r--services/tests/security/forensic/TEST_MAPPING7
-rw-r--r--services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java387
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java48
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt167
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java22
-rw-r--r--tests/BootImageProfileTest/Android.bp1
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt5
-rw-r--r--tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java8
-rw-r--r--tests/testables/src/android/testing/TestWithLooperRule.java3
-rw-r--r--tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt13
-rw-r--r--tools/systemfeatures/tests/golden/RoFeatures.java.gen7
-rw-r--r--tools/systemfeatures/tests/golden/RoNoFeatures.java.gen7
-rw-r--r--tools/systemfeatures/tests/golden/RwFeatures.java.gen7
-rw-r--r--tools/systemfeatures/tests/golden/RwNoFeatures.java.gen7
-rw-r--r--tools/systemfeatures/tests/src/ArrayMap.java26
-rw-r--r--tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java8
286 files changed, 7248 insertions, 2404 deletions
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 11c5b51e23ae..98e53ab97872 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -82,3 +82,10 @@ flag {
description: "Applies the normal quota policy to FGS jobs"
bug: "341201311"
}
+
+flag {
+ name: "adjust_quota_default_constants"
+ namespace: "backstage_power"
+ description: "Adjust quota default parameters"
+ bug: "347058927"
+} \ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 46cc3f01d261..885bad5e31c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -394,13 +394,13 @@ public final class QuotaController extends StateController {
* minutes to run its jobs.
*/
private final long[] mBucketPeriodsMs = new long[]{
- QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS,
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS,
QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS,
0, // NEVER
QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS,
- QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS
+ QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS
};
/** The maximum period any bucket can have. */
@@ -454,7 +454,7 @@ public final class QuotaController extends StateController {
*/
private final long[] mEJLimitsMs = new long[]{
QcConstants.DEFAULT_EJ_LIMIT_ACTIVE_MS,
- QcConstants.DEFAULT_EJ_LIMIT_WORKING_MS,
+ QcConstants.DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS,
QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS,
QcConstants.DEFAULT_EJ_LIMIT_RARE_MS,
0, // NEVER
@@ -476,7 +476,8 @@ public final class QuotaController extends StateController {
/**
* Length of time used to split an app's top time into chunks.
*/
- private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+ private long mEJTopAppTimeChunkSizeMs =
+ QcConstants.DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
/**
* How much EJ quota to give back to an app based on the number of top app time chunks it had.
@@ -486,7 +487,7 @@ public final class QuotaController extends StateController {
/**
* How much EJ quota to give back to an app based on each non-top user interaction.
*/
- private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS;
+ private long mEJRewardInteractionMs = QcConstants.DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS;
/**
* How much EJ quota to give back to an app based on each notification seen event.
@@ -570,6 +571,8 @@ public final class QuotaController extends StateController {
} catch (RemoteException e) {
// ignored; both services live in system_server
}
+
+ processQuotaConstantsAdjustment();
}
@Override
@@ -1411,6 +1414,13 @@ public final class QuotaController extends StateController {
}
}
+ void processQuotaConstantsAdjustment() {
+ if (Flags.adjustQuotaDefaultConstants()) {
+ mQcConstants.adjustDefaultBucketWindowSizes();
+ mQcConstants.adjustDefaultEjLimits();
+ }
+ }
+
@VisibleForTesting
void incrementJobCountLocked(final int userId, @NonNull final String packageName, int count) {
final long now = sElapsedRealtimeClock.millis();
@@ -3112,14 +3122,28 @@ public final class QuotaController extends StateController {
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
30 * 1000L; // 30 seconds
- private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS =
+ // Legacy default window size for EXEMPTED bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS =
DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time
- private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
+ // Legacy default window size for ACTIVE bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS =
DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time
- private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
+ // Legacy default window size for WORKING bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS =
2 * 60 * 60 * 1000L; // 2 hours
- private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
+ // Legacy default window size for FREQUENT bucket
+ private static final long DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS =
8 * 60 * 60 * 1000L; // 8 hours
+
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS =
+ 20 * 60 * 1000L; // 20 minutes.
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS =
+ 30 * 60 * 1000L; // 30 minutes.
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS =
+ 4 * 60 * 60 * 1000L; // 4 hours
+ private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS =
+ 12 * 60 * 60 * 1000L; // 12 hours
+
private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS =
@@ -3133,9 +3157,9 @@ public final class QuotaController extends StateController {
75; // 75/window = 450/hr = 1/session
private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED;
private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
- (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
+ (int) (60.0 * DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
- (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
+ (int) (25.0 * DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
(int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10;
@@ -3156,16 +3180,21 @@ public final class QuotaController extends StateController {
// TODO(267949143): set a different limit for headless system apps
private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 60 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS;
- private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
+ private static final long DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS;
+ private static final long DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS = 15 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS;
private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS = 15 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS = 30 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS;
- private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
+ 30 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
+ 5 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
- private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS;
+ private static final long DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS = 5 * SECOND_IN_MILLIS;
private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0;
private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
@@ -3215,28 +3244,28 @@ public final class QuotaController extends StateController {
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS;
+ public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
+ public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
+ public long WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
* expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past
* WINDOW_SIZE_MS.
*/
- public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
+ public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS;
/**
* The quota window size of the particular standby bucket. Apps in this standby bucket are
@@ -3397,7 +3426,7 @@ public final class QuotaController extends StateController {
* standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring
* in any rewards or free EJs).
*/
- public long EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_WORKING_MS;
+ public long EJ_LIMIT_WORKING_MS = DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS;
/**
* The total expedited job session limit of the particular standby bucket. Apps in this
@@ -3441,7 +3470,7 @@ public final class QuotaController extends StateController {
/**
* Length of time used to split an app's top time into chunks.
*/
- public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+ public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
/**
* How much EJ quota to give back to an app based on the number of top app time chunks it
@@ -3452,7 +3481,7 @@ public final class QuotaController extends StateController {
/**
* How much EJ quota to give back to an app based on each non-top user interaction.
*/
- public long EJ_REWARD_INTERACTION_MS = DEFAULT_EJ_REWARD_INTERACTION_MS;
+ public long EJ_REWARD_INTERACTION_MS = DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS;
/**
* How much EJ quota to give back to an app based on each notification seen event.
@@ -3470,6 +3499,52 @@ public final class QuotaController extends StateController {
*/
public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
+ void adjustDefaultBucketWindowSizes() {
+ WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS;
+ WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS;
+ WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS;
+ WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS;
+
+ mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[EXEMPTED_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS));
+ mBucketPeriodsMs[ACTIVE_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[ACTIVE_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
+ mBucketPeriodsMs[WORKING_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[WORKING_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
+ mBucketPeriodsMs[FREQUENT_INDEX] = Math.max(
+ mAllowedTimePerPeriodMs[FREQUENT_INDEX],
+ Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
+ }
+
+ void adjustDefaultEjLimits() {
+ EJ_LIMIT_WORKING_MS = DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS;
+ EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS;
+ EJ_REWARD_INTERACTION_MS = DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS;
+
+ // The limit must be in the range [15 minutes, active limit].
+ mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS,
+ Math.min(mEJLimitsMs[ACTIVE_INDEX], EJ_LIMIT_WORKING_MS));
+
+ // Limit interaction reward to be in the range [5 seconds, 15 minutes] per event.
+ mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS,
+ Math.max(5 * SECOND_IN_MILLIS, EJ_REWARD_INTERACTION_MS));
+
+ // Limit chunking to be in the range [1 millisecond, 15 minutes] per event.
+ long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS,
+ Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS));
+ mEJTopAppTimeChunkSizeMs = newChunkSizeMs;
+ if (mEJTopAppTimeChunkSizeMs < mEJRewardTopAppMs) {
+ // Not making chunk sizes and top rewards to be the upper/lower
+ // limits of the other to allow trying different policies. Just log
+ // the discrepancy.
+ Slog.w(TAG, "EJ top app time chunk less than reward: "
+ + mEJTopAppTimeChunkSizeMs + " vs " + mEJRewardTopAppMs);
+ }
+ }
+
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
switch (key) {
@@ -3638,7 +3713,9 @@ public final class QuotaController extends StateController {
case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS:
// We don't need to re-evaluate execution stats or constraint status for this.
EJ_TOP_APP_TIME_CHUNK_SIZE_MS =
- properties.getLong(key, DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS);
+ properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS :
+ DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS);
// Limit chunking to be in the range [1 millisecond, 15 minutes] per event.
long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS,
Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS));
@@ -3674,7 +3751,9 @@ public final class QuotaController extends StateController {
case KEY_EJ_REWARD_INTERACTION_MS:
// We don't need to re-evaluate execution stats or constraint status for this.
EJ_REWARD_INTERACTION_MS =
- properties.getLong(key, DEFAULT_EJ_REWARD_INTERACTION_MS);
+ properties.getLong(key, Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS :
+ DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS);
// Limit interaction reward to be in the range [5 seconds, 15 minutes] per
// event.
mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS,
@@ -3748,14 +3827,23 @@ public final class QuotaController extends StateController {
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
DEFAULT_MAX_EXECUTION_TIME_MS);
WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS,
- DEFAULT_WINDOW_SIZE_EXEMPTED_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS);
WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS,
- DEFAULT_WINDOW_SIZE_ACTIVE_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS);
WINDOW_SIZE_WORKING_MS =
- properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
+ properties.getLong(KEY_WINDOW_SIZE_WORKING_MS,
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS);
WINDOW_SIZE_FREQUENT_MS =
properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS,
- DEFAULT_WINDOW_SIZE_FREQUENT_MS);
+ Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS :
+ DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS);
WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS,
DEFAULT_WINDOW_SIZE_RARE_MS);
WINDOW_SIZE_RESTRICTED_MS =
@@ -3926,7 +4014,9 @@ public final class QuotaController extends StateController {
EJ_LIMIT_ACTIVE_MS = properties.getLong(
KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
EJ_LIMIT_WORKING_MS = properties.getLong(
- KEY_EJ_LIMIT_WORKING_MS, DEFAULT_EJ_LIMIT_WORKING_MS);
+ KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants()
+ ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS :
+ DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS);
EJ_LIMIT_FREQUENT_MS = properties.getLong(
KEY_EJ_LIMIT_FREQUENT_MS, DEFAULT_EJ_LIMIT_FREQUENT_MS);
EJ_LIMIT_RARE_MS = properties.getLong(
@@ -4289,6 +4379,13 @@ public final class QuotaController extends StateController {
@Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
+ pw.println("Flags: ");
+ pw.println(" " + Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS
+ + ": " + Flags.adjustQuotaDefaultConstants());
+ pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS
+ + ": " + Flags.enforceQuotaPolicyToFgsJobs());
+ pw.println();
+
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
pw.println();
diff --git a/core/api/current.txt b/core/api/current.txt
index f59b57528ef6..14a91c9033c5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8781,7 +8781,6 @@ package android.app.admin {
package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -8793,9 +8792,7 @@ package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
- method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fa4fc43c3418..349d06ca8ad6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4170,9 +4170,11 @@ package android.content.pm {
}
public class PackageInstaller {
+ method @FlaggedApi("android.content.pm.verification_service") @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public final int getVerificationPolicy();
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
+ method @FlaggedApi("android.content.pm.verification_service") @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public final boolean setVerificationPolicy(int);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
@@ -4183,12 +4185,20 @@ package android.content.pm {
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_DELETE_FLAGS = "android.content.pm.extra.DELETE_FLAGS";
field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
+ field @FlaggedApi("android.content.pm.verification_service") public static final String EXTRA_VERIFICATION_FAILURE_REASON = "android.content.pm.extra.VERIFICATION_FAILURE_REASON";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE = 1; // 0x1
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED = 2; // 0x2
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_CLOSED = 3; // 0x3
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_OPEN = 1; // 0x1
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_WARN = 2; // 0x2
+ field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_NONE = 0; // 0x0
}
public static class PackageInstaller.InstallInfo {
@@ -4635,12 +4645,13 @@ package android.content.pm.verify.pkg {
method @NonNull public android.content.pm.SigningInfo getSigningInfo();
method @NonNull public android.net.Uri getStagedPackageUri();
method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+ method public int getVerificationPolicy();
method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
+ method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public boolean setVerificationPolicy(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
- field public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; // 0x2
field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
field public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0; // 0x0
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 65cd984ac1ba..d12e1bf77e17 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -19,6 +19,7 @@ filegroup {
srcs: [
"**/*.java",
"**/*.aidl",
+ ":systemfeatures-gen-srcs",
":framework-nfc-non-updatable-sources",
":messagequeue-gen",
":ranging_stack_mock_initializer",
@@ -665,3 +666,29 @@ java_library {
}
// protolog end
+
+// Whether to enable read-only system feature codegen.
+gen_readonly_feature_apis = select(release_flag("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS"), {
+ true: "true",
+ false: "false",
+ default: "false",
+})
+
+// Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in
+// details about fixed system features defined by build flags. When disabled,
+// the APIs are simply passthrough stubs with no meaningful side effects.
+genrule {
+ name: "systemfeatures-gen-srcs",
+ cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " +
+ // --readonly=false (default) makes the codegen an effective no-op passthrough API.
+ " --readonly=" + gen_readonly_feature_apis +
+ // For now, only export "android.hardware.type.*" system features APIs.
+ // TODO(b/203143243): Use an intermediate soong var that aggregates all declared
+ // RELEASE_SYSTEM_FEATURE_* declarations into a single arg.
+ " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" +
+ " > $(out)",
+ out: [
+ "RoSystemFeatures.java",
+ ],
+ tools: ["systemfeatures-gen-tool"],
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7273e64846c0..36fc65a76d53 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1031,7 +1031,9 @@ public class ActivityManager {
| PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
/**
- * All implicit capabilities. There are capabilities that process automatically have.
+ * All implicit capabilities. This capability set is currently only used for processes under
+ * active instrumentation. The intent is to allow CTS tests to always have these capabilities
+ * so that every test doesn't need to launch FGS.
* @hide
*/
@TestApi
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 3bd121a4a19b..f80121d0c9b6 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1328,7 +1328,8 @@ public abstract class ActivityManagerInternal {
* Add a creator token for all embedded intents (stored as extra) of the given intent.
*
* @param intent The given intent
+ * @param creatorPackage the package name of the creator app.
* @hide
*/
- public abstract void addCreatorToken(Intent intent);
+ public abstract void addCreatorToken(Intent intent, String creatorPackage);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 99625ac20e60..e7f4dbc24022 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7346,6 +7346,8 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
+ VMDebug.setUserId(UserHandle.myUserId());
+ VMDebug.addApplication(data.appInfo.packageName);
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName,
@@ -7868,9 +7870,20 @@ public final class ActivityThread extends ClientTransactionHandler
file.getParentFile().mkdirs();
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
+
+ if (ii.packageName != null) {
+ VMDebug.addApplication(ii.packageName);
+ }
}
private void handleFinishInstrumentationWithoutRestart() {
+ LoadedApk loadedApk = getApplication().mLoadedApk;
+ // Only remove instrumentation app if this was not a self-testing app.
+ if (mInstrumentationPackageName != null && loadedApk != null && !mInstrumentationPackageName
+ .equals(loadedApk.mPackageName)) {
+ VMDebug.removeApplication(mInstrumentationPackageName);
+ }
+
mInstrumentation.onDestroy();
mInstrumentationPackageName = null;
mInstrumentationAppDir = null;
@@ -8904,6 +8917,11 @@ public final class ActivityThread extends ClientTransactionHandler
return false;
}
+ void addApplication(@NonNull Application app) {
+ mAllApplications.add(app);
+ VMDebug.addApplication(app.mLoadedApk.mPackageName);
+ }
+
@Override
public boolean isInDensityCompatMode() {
return mDensityCompatMode;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1df8f63aa402..1e45d6fd1674 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1478,7 +1478,7 @@ public final class LoadedApk {
+ " package " + mPackageName + ": " + e.toString(), e);
}
}
- mActivityThread.mAllApplications.add(app);
+ mActivityThread.addApplication(app);
mApplication = app;
if (!allowDuplicateInstances) {
synchronized (sApplications) {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index d363e19bcc19..ba71afb49629 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -97,6 +97,7 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve
# Performance
per-file PropertyInvalidatedCache.java = file:/PERFORMANCE_OWNERS
+per-file performance.aconfig = file:/PERFORMANCE_OWNERS
# Pinner
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 439d988e2588..dca433696fe7 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -110,40 +110,6 @@ public final class AppFunctionManager {
*
* @param request the request to execute the app function
* @param executor the executor to run the callback
- * @param callback the callback to receive the function execution result. if the calling app
- * does not own the app function or does not have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
- * ExecuteAppFunctionResponse.RESULT_DENIED}.
- * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor,
- * CancellationSignal, Consumer)} instead. This method will be removed once usage references
- * are updated.
- */
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
- @UserHandleAware
- @Deprecated
- public void executeAppFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- executeAppFunction(request, executor, new CancellationSignal(), callback);
- }
-
- /**
- * Executes the app function.
- *
- * <p>Note: Applications can execute functions they define. To execute functions defined in
- * another component, apps would need to have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS}.
- *
- * @param request the request to execute the app function
- * @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
* @param callback the callback to receive the function execution result. if the calling app
* does not own the app function or does not have {@code
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index ceca850a1037..63d187aa11ef 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -158,74 +158,6 @@ public abstract class AppFunctionService extends Service {
* thread and dispatch the result with the given callback. You should always report back the
* result using the callback, no matter if the execution was successful or not.
*
- * @param request The function execution request.
- * @param callback A callback to report back the result.
- * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
- * Consumer)} instead. This method will be removed once usage references are updated.
- */
- @MainThread
- @Deprecated
- public void onExecuteFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- Log.w(
- "AppFunctionService",
- "Calling deprecated default implementation of onExecuteFunction");
- }
-
- /**
- * Called by the system to execute a specific app function.
- *
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
- *
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
- *
- * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
- * thread and dispatch the result with the given callback. You should always report back the
- * result using the callback, no matter if the execution was successful or not.
- *
- * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
- * the execution of function if requested by the system.
- *
- * @param request The function execution request.
- * @param cancellationSignal A signal to cancel the execution.
- * @param callback A callback to report back the result.
- * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String,
- * CancellationSignal, Consumer)} instead. This method will be removed once usage references
- * are updated.
- */
- @MainThread
- @Deprecated
- public void onExecuteFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- onExecuteFunction(request, callback);
- }
-
- /**
- * Called by the system to execute a specific app function.
- *
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
- *
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
- *
- * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
- * thread and dispatch the result with the given callback. You should always report back the
- * result using the callback, no matter if the execution was successful or not.
- *
* <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
* the execution of function if requested by the system.
*
@@ -235,11 +167,9 @@ public abstract class AppFunctionService extends Service {
* @param callback A callback to report back the result.
*/
@MainThread
- public void onExecuteFunction(
+ public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- onExecuteFunction(request, cancellationSignal, callback);
- }
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index c0f13ca557e2..ecf4cd115fef 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -75,7 +75,7 @@ public final class UsageEventsQuery implements Parcelable {
}
/**
- * Returns the exclusive timpstamp to indicate the end of the range of events.
+ * Returns the exclusive timestamp to indicate the end of the range of events.
* Defined in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}.
*/
public @CurrentTimeMillisLong long getEndTimeMillis() {
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 7f30d7cccb57..124973489dd1 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -287,7 +287,7 @@ public final class AssociationInfo implements Parcelable {
/**
* Get the device icon of the associated device. The device icon represents the device type.
*
- * @return the device icon, or {@code null} if no device icon is has been set for the
+ * @return the device icon, or {@code null} if no device icon has been set for the
* associated device.
*
* @see AssociationRequest.Builder#setDeviceIcon(Icon)
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 41a6791d8a7b..f368935a74c8 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -475,8 +475,8 @@ public final class AssociationRequest implements Parcelable {
}
/**
- * Set the device icon for the self-managed device and this icon will be
- * displayed in the self-managed association dialog.
+ * Set the device icon for the self-managed device and to display the icon in the
+ * self-managed association dialog.
*
* @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp
* or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}.
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index dfad6de4ba16..4472c3d13d7c 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -478,6 +478,15 @@ public final class CompanionDeviceManager {
Objects.requireNonNull(callback, "Callback cannot be null");
handler = Handler.mainIfNull(handler);
+ if (Flags.associationDeviceIcon()) {
+ final Icon deviceIcon = request.getDeviceIcon();
+
+ if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
+ throw new IllegalArgumentException("The size of the device icon must be "
+ + "24dp x 24dp to ensure proper display");
+ }
+ }
+
try {
mService.associate(request, new AssociationRequestCallbackProxy(handler, callback),
mContext.getOpPackageName(), mContext.getUserId());
@@ -542,11 +551,13 @@ public final class CompanionDeviceManager {
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(callback, "Callback cannot be null");
- final Icon deviceIcon = request.getDeviceIcon();
+ if (Flags.associationDeviceIcon()) {
+ final Icon deviceIcon = request.getDeviceIcon();
- if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
- throw new IllegalArgumentException("The size of the device icon must be 24dp x 24dp to"
- + "ensure proper display");
+ if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) {
+ throw new IllegalArgumentException("The size of the device icon must be "
+ + "24dp x 24dp to ensure proper display");
+ }
}
try {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 91f7a8bae163..628435da3a37 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5658,6 +5658,14 @@ public abstract class Context {
public static final String BINARY_TRANSPARENCY_SERVICE = "transparency";
/**
+ * System service name for ForensicService.
+ * The service manages the forensic info on device.
+ * @hide
+ */
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ public static final String FORENSIC_SERVICE = "forensic";
+
+ /**
* System service name for the DeviceIdleManager.
* @see #getSystemService(String)
* @hide
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0bb0027fb0c3..f71952849872 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -888,6 +888,22 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_ACTIVITY_RECOGNIZER =
"android.intent.action.ACTIVITY_RECOGNIZER";
+ /** @hide */
+ public static void maybeMarkAsMissingCreatorToken(Object object) {
+ if (object instanceof Intent intent) {
+ maybeMarkAsMissingCreatorTokenInternal(intent);
+ }
+ }
+
+ private static void maybeMarkAsMissingCreatorTokenInternal(Intent intent) {
+ boolean isForeign = (intent.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0;
+ boolean isWithoutTrustedCreatorToken =
+ (intent.mLocalFlags & Intent.LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT) == 0;
+ if (isForeign && isWithoutTrustedCreatorToken) {
+ intent.addExtendedFlags(EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN);
+ }
+ }
+
/**
* Represents a shortcut/live folder icon resource.
*
@@ -7684,10 +7700,8 @@ public class Intent implements Parcelable, Cloneable {
/**
* This flag indicates the creator token of this intent has been verified.
- *
- * @hide
*/
- public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6;
+ private static final int LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT = 1 << 6;
/** @hide */
@IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
@@ -12243,6 +12257,30 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ /** @hide */
+ public void checkCreatorToken() {
+ if (mExtras == null) return;
+ if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) {
+ for (String key : mCreatorTokenInfo.mExtraIntentKeys) {
+ try {
+ Intent extraIntent = mExtras.getParcelable(key, Intent.class);
+ if (extraIntent == null) {
+ Log.w(TAG, "The key {" + key
+ + "} does not correspond to an intent in the bundle.");
+ continue;
+ }
+ extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e);
+ }
+ }
+ }
+ // mark the bundle as intent extras after calls to getParcelable.
+ // otherwise, the logic to mark missing token would run before
+ // mark trusted creator token present.
+ mExtras.setIsIntentExtra();
+ }
+
public void writeToParcel(Parcel out, int flags) {
out.writeString8(mAction);
Uri.writeToParcel(out, mData);
@@ -12730,6 +12768,7 @@ public class Intent implements Parcelable, Cloneable {
}
mLocalFlags |= localFlags;
+ checkCreatorToken();
// Special attribution fix-up logic for any BluetoothDevice extras
// passed via Bluetooth intents
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 451c0e5e079a..c911326ccffd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -93,4 +93,10 @@ interface IPackageInstaller {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ int getVerificationPolicy();
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ boolean setVerificationPolicy(int policy);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c673d5846d5d..cba7bc912666 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -62,6 +62,8 @@ import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.pm.verify.domain.DomainSet;
+import android.content.pm.verify.pkg.VerificationSession;
+import android.content.pm.verify.pkg.VerificationStatus;
import android.graphics.Bitmap;
import android.icu.util.ULocale;
import android.net.Uri;
@@ -418,6 +420,21 @@ public class PackageInstaller {
public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS";
/**
+ * When verification is blocked as part of the installation, additional reason for the block
+ * will be provided to the installer with a {@link VerificationFailedReason} as part of the
+ * installation result returned via the {@link IntentSender} in
+ * {@link Session#commit(IntentSender)}. This extra is provided only when the installation has
+ * failed. Installers can use this extra to check if the installation failure was caused by a
+ * verification failure.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final String EXTRA_VERIFICATION_FAILURE_REASON =
+ "android.content.pm.extra.VERIFICATION_FAILURE_REASON";
+
+ /**
* Streaming installation pending.
* Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
*
@@ -760,6 +777,90 @@ public class PackageInstaller {
@Retention(RetentionPolicy.SOURCE)
public @interface UnarchivalStatus {}
+ /**
+ * Verification failed because of unknown reasons, such as when the verifier times out or cannot
+ * be connected. It can also corresponds to the status of
+ * {@link VerificationSession#VERIFICATION_INCOMPLETE_UNKNOWN} reported by the verifier via
+ * {@link VerificationSession#reportVerificationIncomplete(int)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_FAILED_REASON_UNKNOWN = 0;
+
+ /**
+ * Verification failed because the network is unavailable. This corresponds to the status of
+ * {@link VerificationSession#VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE} reported by the
+ * verifier via {@link VerificationSession#reportVerificationIncomplete(int)}.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE = 1;
+
+ /**
+ * Verification failed because the package is blocked, as reported by the verifier via
+ * {@link VerificationSession#reportVerificationComplete(VerificationStatus)} or
+ * {@link VerificationSession#reportVerificationComplete(VerificationStatus, PersistableBundle)}
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ VERIFICATION_FAILED_REASON_UNKNOWN,
+ VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE,
+ VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED,
+ })
+ public @interface VerificationFailedReason {
+ }
+
+ /**
+ * Do not block installs, regardless of verification status.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_POLICY_NONE = 0; // platform default
+ /**
+ * Only block installations on {@link #VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_POLICY_BLOCK_FAIL_OPEN = 1;
+ /**
+ * Only block installations on {@link #VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED} and ask the
+ * user if they'd like to install anyway when the verification is blocked for other reason.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_POLICY_BLOCK_FAIL_WARN = 2;
+ /**
+ * Block installations whose verification status is blocked for any reason.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ public static final int VERIFICATION_POLICY_BLOCK_FAIL_CLOSED = 3;
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ VERIFICATION_POLICY_NONE,
+ VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
+ VERIFICATION_POLICY_BLOCK_FAIL_WARN,
+ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VerificationPolicy {
+ }
/** Default set of checksums - includes all available checksums.
* @see Session#requestChecksums */
@@ -1503,6 +1604,40 @@ public class PackageInstaller {
}
/**
+ * Return the current verification enforcement policy. This may only be called by the
+ * package currently set by the system as the verifier agent.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public final @VerificationPolicy int getVerificationPolicy() {
+ try {
+ return mInstaller.getVerificationPolicy();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the current verification enforcement policy which will be applied to all the future
+ * installation sessions. This may only be called by the package currently set by the system as
+ * the verifier agent.
+ * @hide
+ * @return whether the new policy was successfully set.
+ */
+ @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public final boolean setVerificationPolicy(@VerificationPolicy int policy) {
+ try {
+ return mInstaller.setVerificationPolicy(policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* An installation that is being actively staged. For an install to succeed,
* all existing and new packages must have identical package names, version
* codes, and signing certificates.
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 52d733314eb6..5b38942d468d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -334,3 +334,11 @@ flag {
bug: "364771256"
is_fixed_read_only: true
}
+
+flag {
+ name: "reduce_broadcasts_for_component_state_changes"
+ namespace: "package_manager_service"
+ description: "Feature flag to limit sending of the PACKAGE_CHANGED broadcast to only the system and the application itself during component state changes."
+ bug: "292261144"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 7de7131fc2ad..fa3bc9e16a6b 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -211,6 +211,18 @@ flag {
}
flag {
+ name: "property_invalidated_cache_bypass_mismatched_uids"
+ namespace: "multiuser"
+ description: "Bypass the cache when the process UID does not match the binder UID."
+ bug: "373752556"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+
+flag {
name: "cache_profile_parent_read_only"
namespace: "multiuser"
description: "Cache getProfileParent to avoid unnecessary binder calls"
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
index 7a9484abd1b1..036c1e69cb0d 100644
--- a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -25,4 +25,6 @@ interface IVerificationSessionInterface {
long getTimeoutTime(int verificationId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
long extendTimeRemaining(int verificationId, long additionalMs);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+ boolean setVerificationPolicy(int verificationId, int policy);
} \ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
index 70b4a022f521..f393be829aed 100644
--- a/core/java/android/content/pm/verify/pkg/VerificationSession.java
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.pm.Flags;
+import android.content.pm.PackageInstaller;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningInfo;
import android.net.Uri;
@@ -52,17 +53,12 @@ public final class VerificationSession implements Parcelable {
* The verification cannot be completed because the network is unavailable.
*/
public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1;
- /**
- * The verification cannot be completed because the network is limited.
- */
- public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2;
/**
* @hide
*/
@IntDef(prefix = {"VERIFICATION_INCOMPLETE_"}, value = {
VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE,
- VERIFICATION_INCOMPLETE_NETWORK_LIMITED,
VERIFICATION_INCOMPLETE_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
@@ -85,6 +81,15 @@ public final class VerificationSession implements Parcelable {
private final IVerificationSessionInterface mSession;
@NonNull
private final IVerificationSessionCallback mCallback;
+ /**
+ * The current policy that is active for the session. It might not be
+ * the same as the original policy that was initially assigned for this verification session,
+ * because the active policy can be overridden by {@link #setVerificationPolicy(int)}.
+ * <p>To improve the latency, store the original policy value and any changes made to it,
+ * so that {@link #getVerificationPolicy()} does not need to make a binder call to retrieve the
+ * currently active policy.</p>
+ */
+ private volatile @PackageInstaller.VerificationPolicy int mVerificationPolicy;
/**
* Constructor used by the system to describe the details of a verification session.
@@ -94,6 +99,7 @@ public final class VerificationSession implements Parcelable {
@NonNull Uri stagedPackageUri, @NonNull SigningInfo signingInfo,
@NonNull List<SharedLibraryInfo> declaredLibraries,
@NonNull PersistableBundle extensionParams,
+ @PackageInstaller.VerificationPolicy int defaultPolicy,
@NonNull IVerificationSessionInterface session,
@NonNull IVerificationSessionCallback callback) {
mId = id;
@@ -103,6 +109,7 @@ public final class VerificationSession implements Parcelable {
mSigningInfo = signingInfo;
mDeclaredLibraries = declaredLibraries;
mExtensionParams = extensionParams;
+ mVerificationPolicy = defaultPolicy;
mSession = session;
mCallback = callback;
}
@@ -144,7 +151,7 @@ public final class VerificationSession implements Parcelable {
/**
* Returns a mapping of any shared libraries declared in the manifest
- * to the {@link SharedLibraryInfo#Type} that is declared. This will be an empty
+ * to the {@link SharedLibraryInfo.Type} that is declared. This will be an empty
* map if no shared libraries are declared by the package.
*/
@NonNull
@@ -174,6 +181,39 @@ public final class VerificationSession implements Parcelable {
}
/**
+ * Return the current policy that is active for this session.
+ * <p>If the policy for this session has been changed by {@link #setVerificationPolicy},
+ * the return value of this method is the current policy that is active for this session.
+ * Otherwise, the return value is the same as the initial policy that was assigned to the
+ * session when it was first created.</p>
+ */
+ public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
+ return mVerificationPolicy;
+ }
+
+ /**
+ * Override the verification policy for this session.
+ * @return True if the override was successful, False otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
+ public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
+ if (mVerificationPolicy == policy) {
+ // No effective policy change
+ return true;
+ }
+ try {
+ if (mSession.setVerificationPolicy(mId, policy)) {
+ mVerificationPolicy = policy;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Extend the timeout for this session by the provided additionalMs to
* fetch relevant information over the network or wait for the network.
* This may be called multiple times. If the request would bypass any max
@@ -239,6 +279,7 @@ public final class VerificationSession implements Parcelable {
mSigningInfo = SigningInfo.CREATOR.createFromParcel(in);
mDeclaredLibraries = in.createTypedArrayList(SharedLibraryInfo.CREATOR);
mExtensionParams = in.readPersistableBundle(getClass().getClassLoader());
+ mVerificationPolicy = in.readInt();
mSession = IVerificationSessionInterface.Stub.asInterface(in.readStrongBinder());
mCallback = IVerificationSessionCallback.Stub.asInterface(in.readStrongBinder());
}
@@ -257,6 +298,7 @@ public final class VerificationSession implements Parcelable {
mSigningInfo.writeToParcel(dest, flags);
dest.writeTypedList(mDeclaredLibraries);
dest.writePersistableBundle(mExtensionParams);
+ dest.writeInt(mVerificationPolicy);
dest.writeStrongBinder(mSession.asBinder());
dest.writeStrongBinder(mCallback.asBinder());
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 49ab15a40a8e..50121278f0e6 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
import android.util.ArrayMap;
import android.util.Log;
import android.util.MathUtils;
@@ -401,6 +402,9 @@ public class BaseBundle {
synchronized (this) {
object = unwrapLazyValueFromMapLocked(i, clazz, itemTypes);
}
+ if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) {
+ Intent.maybeMarkAsMissingCreatorToken(object);
+ }
}
return (clazz != null) ? clazz.cast(object) : (T) object;
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index ed4037c7d246..c18fb0c38b82 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -62,6 +62,12 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@VisibleForTesting
static final int FLAG_HAS_BINDERS = 1 << 12;
+ /**
+ * Indicates there may be intents with creator tokens contained in this bundle. When unparceled,
+ * they should be verified if tokens are missing or invalid.
+ */
+ static final int FLAG_VERIFY_TOKENS_PRESENT = 1 << 13;
+
/**
* Status when the Bundle can <b>assert</b> that the underlying Parcel DOES NOT contain
@@ -274,6 +280,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
return orig;
}
+ /** {@hide} */
+ public void setIsIntentExtra() {
+ mFlags |= FLAG_VERIFY_TOKENS_PRESENT;
+ }
+
/**
* Mark if this Bundle is okay to "defuse." That is, it's okay for system
* processes to ignore any {@link BadParcelableException} encountered when
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a55398ac9752..ef1e6c9405f3 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -114,6 +114,7 @@ public final class Debug
"opengl-tracing",
"view-hierarchy",
"support_boot_stages",
+ "app_info",
};
/**
@@ -1016,14 +1017,14 @@ public final class Debug
// send VM_START.
System.out.println("Waiting for debugger first packet");
- mWaiting = true;
+ setWaitingForDebugger(true);
while (!isDebuggerConnected()) {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
}
}
- mWaiting = false;
+ setWaitingForDebugger(false);
System.out.println("Debug.suspendAllAndSentVmStart");
VMDebug.suspendAllAndSendVmStart();
@@ -1049,12 +1050,12 @@ public final class Debug
Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
DdmServer.sendChunk(waitChunk);
- mWaiting = true;
+ setWaitingForDebugger(true);
while (!isDebuggerConnected()) {
try { Thread.sleep(SPIN_DELAY); }
catch (InterruptedException ie) {}
}
- mWaiting = false;
+ setWaitingForDebugger(false);
System.out.println("Debugger has connected");
@@ -1112,6 +1113,16 @@ public final class Debug
}
/**
+ * Set whether the app is waiting for a debugger to connect
+ *
+ * @hide
+ */
+ private static void setWaitingForDebugger(boolean waiting) {
+ mWaiting = waiting;
+ VMDebug.setWaitingForDebugger(waiting);
+ }
+
+ /**
* Returns an array of strings that identify Framework features. This is
* used by DDMS to determine what sorts of operations the Framework can
* perform.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 346ee7ca4f87..71d29af6cf02 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -41,6 +41,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
import com.android.sdksandbox.flags.Flags;
+import dalvik.system.VMDebug;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -1410,6 +1411,7 @@ public class Process {
public static void setArgV0(@NonNull String text) {
sArgV0 = text;
setArgV0Native(text);
+ VMDebug.setCurrentProcessName(text);
}
private static native void setArgV0Native(String text);
diff --git a/core/java/android/security/forensic/IForensicService.aidl b/core/java/android/security/forensic/IForensicService.aidl
new file mode 100644
index 000000000000..a944b18cb26d
--- /dev/null
+++ b/core/java/android/security/forensic/IForensicService.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.security.forensic;
+
+import android.security.forensic.IForensicServiceCommandCallback;
+import android.security.forensic.IForensicServiceStateCallback;
+
+/**
+ * Binder interface to communicate with ForensicService.
+ * @hide
+ */
+interface IForensicService {
+ void monitorState(IForensicServiceStateCallback callback);
+ void makeVisible(IForensicServiceCommandCallback callback);
+ void makeInvisible(IForensicServiceCommandCallback callback);
+ void enable(IForensicServiceCommandCallback callback);
+ void disable(IForensicServiceCommandCallback callback);
+}
diff --git a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl
new file mode 100644
index 000000000000..7fa0c7f72690
--- /dev/null
+++ b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.forensic;
+
+/**
+ * @hide
+ */
+ oneway interface IForensicServiceCommandCallback {
+ @Backing(type="int")
+ enum ErrorCode{
+ UNKNOWN = 0,
+ PERMISSION_DENIED = 1,
+ INVALID_STATE_TRANSITION = 2,
+ BACKUP_TRANSPORT_UNAVAILABLE = 3,
+ DATA_SOURCE_UNAVAILABLE = 3,
+ }
+ void onSuccess();
+ void onFailure(ErrorCode error);
+ }
diff --git a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl
new file mode 100644
index 000000000000..0cda35083ffd
--- /dev/null
+++ b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.security.forensic;
+
+/**
+ * @hide
+ */
+ oneway interface IForensicServiceStateCallback {
+ @Backing(type="int")
+ enum State{
+ UNKNOWN = 0,
+ INVISIBLE = 1,
+ VISIBLE = 2,
+ ENABLED = 3,
+ }
+ void onStateChange(State state);
+ }
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 9f54d9fca24b..3adbd686cd2c 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -24,7 +24,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.Network;
-import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
import android.net.SntpClient;
import android.os.Build;
import android.os.SystemClock;
@@ -687,16 +687,8 @@ public abstract class NtpTrustedTime implements TrustedTime {
if (connectivityManager == null) {
return false;
}
- final NetworkCapabilities networkCapabilities =
- connectivityManager.getNetworkCapabilities(network);
- if (networkCapabilities == null) {
- if (LOGD) Log.d(TAG, "getNetwork: failed to get network capabilities");
- return false;
- }
- final boolean isConnectedToInternet = networkCapabilities.hasCapability(
- NetworkCapabilities.NET_CAPABILITY_INTERNET)
- && networkCapabilities.hasCapability(
- NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+
// This connectivity check is to avoid performing a DNS lookup for the time server on a
// unconnected network. There are races to obtain time in Android when connectivity
// changes, which means that forceRefresh() can be called by various components before
@@ -706,8 +698,8 @@ public abstract class NtpTrustedTime implements TrustedTime {
// A side effect of check is that tests that run a fake NTP server on the device itself
// will only be able to use it if the active network is connected, even though loopback
// addresses are actually reachable.
- if (!isConnectedToInternet) {
- if (LOGD) Log.d(TAG, "getNetwork: no internet connectivity");
+ if (ni == null || !ni.isConnected()) {
+ if (LOGD) Log.d(TAG, "getNetwork: no connectivity");
return false;
}
return true;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 182ed1ebad59..1f17e8ec7b85 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5952,7 +5952,34 @@ public final class ViewRootImpl implements ViewParent,
// If no intersection, set bounds to empty.
bounds.setEmpty();
}
- return !bounds.isEmpty();
+
+ if (bounds.isEmpty()) {
+ return false;
+ }
+
+ if (android.view.accessibility.Flags.focusRectMinSize()) {
+ adjustAccessibilityFocusedRectBoundsIfNeeded(bounds);
+ }
+
+ return true;
+ }
+
+ /**
+ * Adjusts accessibility focused rect bounds so that they are not invisible.
+ *
+ * <p>Focus bounds smaller than double the stroke width are very hard to see (or invisible).
+ * Expand the focus bounds if necessary to at least double the stroke width.
+ * @param bounds The bounds to adjust
+ */
+ @VisibleForTesting
+ public void adjustAccessibilityFocusedRectBoundsIfNeeded(Rect bounds) {
+ final int minRectLength = mAccessibilityManager.getAccessibilityFocusStrokeWidth() * 2;
+ if (bounds.width() < minRectLength || bounds.height() < minRectLength) {
+ final float missingWidth = Math.max(0, minRectLength - bounds.width());
+ final float missingHeight = Math.max(0, minRectLength - bounds.height());
+ bounds.inset(-1 * (int) Math.ceil(missingWidth / 2),
+ -1 * (int) Math.ceil(missingHeight / 2));
+ }
}
private Drawable getAccessibilityFocusedDrawable() {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 8ffae845de1f..820a1fba11ad 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -96,6 +96,16 @@ flag {
flag {
namespace: "accessibility"
+ name: "focus_rect_min_size"
+ description: "Ensures the a11y focus rect is big enough to be drawn as visible"
+ bug: "368667566"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "accessibility"
name: "force_invert_color"
description: "Enable force force-dark for smart inversion and dark theme everywhere"
bug: "282821643"
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 18129530978f..45f6480b0b7f 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -308,6 +308,13 @@ flag {
}
flag {
+ name: "enable_restore_to_previous_size_from_desktop_immersive"
+ namespace: "lse_desktop_experience"
+ description: "Restores the window bounds to their previous size when exiting desktop immersive"
+ bug: "372318163"
+}
+
+flag {
name: "enable_display_focus_in_shell_transitions"
namespace: "lse_desktop_experience"
description: "Creates a shell transition when display focus switches."
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c201fc42df3..571fe0ba37b2 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -96,6 +96,7 @@ public class ProtoLogViewerConfigReader {
logger.log("Unloading viewer config hash " + hash);
mLogMessageMap.remove(hash);
}
+ mGroupHashes.remove(group);
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5693d666adc2..5522aa09a32e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8479,6 +8479,18 @@
android:protectionLevel="internal"
android:featureFlag="android.content.pm.verification_service" />
+ <!--
+ This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component
+ state of a non-exported component has been changed.
+ <p>Not for use by third-party applications. </p>
+ <p>Protection level: internal
+ @hide
+ -->
+ <permission
+ android:name="android.permission.RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
+ android:protectionLevel="internal"
+ android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index d35bfb7bc1a1..352c3904406c 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -212,9 +212,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -314,9 +314,9 @@ easier.
<style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Material.NoActionBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -431,9 +431,9 @@ easier.
<style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Material.NoActionBar.Fullscreen">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -550,9 +550,9 @@ easier.
<style name="Theme.DeviceDefault.NoActionBar.Overscan" parent="Theme.Material.NoActionBar.Overscan">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -668,9 +668,9 @@ easier.
<style name="Theme.DeviceDefault.NoActionBar.TranslucentDecor" parent="Theme.Material.NoActionBar.TranslucentDecor">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -801,9 +801,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -911,9 +911,9 @@ easier.
<style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Material.Dialog.MinWidth">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1027,9 +1027,9 @@ easier.
<style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Material.Dialog.NoActionBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1144,9 +1144,9 @@ easier.
<style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Material.Dialog.NoActionBar.MinWidth">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1277,9 +1277,9 @@ easier.
<style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Material.DialogWhenLarge">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1395,9 +1395,9 @@ easier.
<style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Material.DialogWhenLarge.NoActionBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1511,9 +1511,9 @@ easier.
<style name="Theme.DeviceDefault.Dialog.Presentation" parent="Theme.Material.Dialog.Presentation">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1629,9 +1629,9 @@ easier.
<style name="Theme.DeviceDefault.Panel" parent="Theme.Material.Panel">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1746,9 +1746,9 @@ easier.
<style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Material.Wallpaper">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1863,9 +1863,9 @@ easier.
<style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="Theme.Material.Wallpaper.NoTitleBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -1980,9 +1980,9 @@ easier.
<style name="Theme.DeviceDefault.InputMethod" parent="Theme.Material.InputMethod">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -2097,9 +2097,9 @@ easier.
<style name="Theme.DeviceDefault.VoiceInteractionSession" parent="Theme.Material.VoiceInteractionSession">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -2218,9 +2218,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -2336,9 +2336,9 @@ easier.
<style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -2451,9 +2451,9 @@ easier.
<style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -2720,9 +2720,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -2821,9 +2821,9 @@ easier.
<style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Material.Light.DarkActionBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
- <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -2937,9 +2937,9 @@ easier.
<style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Material.Light.NoActionBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3054,9 +3054,9 @@ easier.
<style name="Theme.DeviceDefault.Light.NoActionBar.Fullscreen" parent="Theme.Material.Light.NoActionBar.Fullscreen">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3173,9 +3173,9 @@ easier.
<style name="Theme.DeviceDefault.Light.NoActionBar.Overscan" parent="Theme.Material.Light.NoActionBar.Overscan">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3291,9 +3291,9 @@ easier.
<style name="Theme.DeviceDefault.Light.NoActionBar.TranslucentDecor" parent="Theme.Material.Light.NoActionBar.TranslucentDecor">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3426,9 +3426,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3535,9 +3535,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3654,9 +3654,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3774,9 +3774,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3895,9 +3895,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -3996,9 +3996,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4096,9 +4096,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4217,9 +4217,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4336,9 +4336,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4454,9 +4454,9 @@ easier.
<style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Material.Light.Panel">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4571,9 +4571,9 @@ easier.
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4688,9 +4688,9 @@ easier.
<style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4803,9 +4803,9 @@ easier.
<style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
- <item name="colorPrimaryDark">@color/primary_device_default_light</item>
+ <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -4937,7 +4937,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5044,7 +5044,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5148,7 +5148,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5245,7 +5245,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
@@ -5361,7 +5361,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5487,7 +5487,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5606,7 +5606,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
<item name="colorSecondary">@color/secondary_device_default_settings</item>
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
<item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
@@ -5788,7 +5788,7 @@ easier.
<style name="ThemeOverlay.DeviceDefault.Accent">
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
@@ -5863,7 +5863,7 @@ easier.
<style name="ThemeOverlay.DeviceDefault.Accent.Light">
<item name="colorAccent">@color/accent_device_default_light</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
@@ -5942,7 +5942,7 @@ easier.
<style name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" parent="ThemeOverlay.Material.Dark.ActionBar">
<item name="colorAccent">@color/accent_device_default_dark</item>
- <item name="colorAccentPrimary">@color/system_primary_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
<item name="colorAccentSecondary">@color/system_secondary_dark</item>
<item name="colorAccentTertiary">@color/system_tertiary_dark</item>
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index 5636f9bc436c..6d14ccb18c53 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -14,3 +14,5 @@ per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/
# Files related to background activity launches
per-file Background*Start* = file:/BAL_OWNERS
+# Files related to caching
+per-file PropertyInvalidatedCache* = file:/PERFORMANCE_OWNERS
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
index 987f68d4f9e1..80255c5f6600 100644
--- a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -16,6 +16,9 @@
package android.content.pm.verify;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -23,6 +26,8 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.pm.SharedLibraryInfo;
@@ -73,6 +78,7 @@ public class VerificationSessionTest {
private static final long TEST_EXTEND_TIME = 2000L;
private static final String TEST_KEY = "test key";
private static final String TEST_VALUE = "test value";
+ private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
private final PersistableBundle mTestExtensionParams = new PersistableBundle();
@@ -90,7 +96,7 @@ public class VerificationSessionTest {
mTestExtensionParams.putString(TEST_KEY, TEST_VALUE);
mTestSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, mTestDeclaredLibraries,
- mTestExtensionParams, mTestSessionInterface, mTestCallback);
+ mTestExtensionParams, TEST_POLICY, mTestSessionInterface, mTestCallback);
}
@Test
@@ -118,6 +124,7 @@ public class VerificationSessionTest {
// structure is different, but all the key/value pairs should be preserved as before.
assertThat(sessionFromParcel.getExtensionParams().getString(TEST_KEY))
.isEqualTo(mTestExtensionParams.getString(TEST_KEY));
+ assertThat(sessionFromParcel.getVerificationPolicy()).isEqualTo(TEST_POLICY);
}
@Test
@@ -152,4 +159,42 @@ public class VerificationSessionTest {
verify(mTestCallback, times(1)).reportVerificationIncomplete(
eq(TEST_ID), eq(reason));
}
+
+ @Test
+ public void testPolicyNoOverride() {
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY);
+ // This "set" is a no-op
+ assertThat(mTestSession.setVerificationPolicy(TEST_POLICY)).isTrue();
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY);
+ verifyZeroInteractions(mTestSessionInterface);
+ }
+
+ @Test
+ public void testPolicyOverrideFail() throws Exception {
+ final int newPolicy = VERIFICATION_POLICY_BLOCK_FAIL_WARN;
+ when(mTestSessionInterface.setVerificationPolicy(anyInt(), anyInt())).thenReturn(false);
+ assertThat(mTestSession.setVerificationPolicy(newPolicy)).isFalse();
+ verify(mTestSessionInterface, times(1))
+ .setVerificationPolicy(eq(TEST_ID), eq(newPolicy));
+ // Next "get" should not trigger binder call because the previous "set" has failed
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY);
+ verifyNoMoreInteractions(mTestSessionInterface);
+ }
+
+ @Test
+ public void testPolicyOverrideSuccess() throws Exception {
+ final int newPolicy = VERIFICATION_POLICY_BLOCK_FAIL_WARN;
+ when(mTestSessionInterface.setVerificationPolicy(anyInt(), anyInt())).thenReturn(true);
+ assertThat(mTestSession.setVerificationPolicy(newPolicy)).isTrue();
+ verify(mTestSessionInterface, times(1))
+ .setVerificationPolicy(eq(TEST_ID), eq(newPolicy));
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(newPolicy);
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(newPolicy);
+
+ // Setting back to the original policy should still trigger binder calls
+ assertThat(mTestSession.setVerificationPolicy(TEST_POLICY)).isTrue();
+ verify(mTestSessionInterface, times(1))
+ .setVerificationPolicy(eq(TEST_ID), eq(TEST_POLICY));
+ assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY);
+ }
}
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
index 7f73a1eb4b48..7807c8a94530 100644
--- a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
+++ b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java
@@ -16,6 +16,8 @@
package android.content.pm.verify;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -52,6 +54,7 @@ public class VerifierServiceTest {
private static final String TEST_PACKAGE_NAME = "com.foo";
private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test");
private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo();
+ private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
private VerifierService mService;
private VerificationSession mSession;
@@ -60,8 +63,7 @@ public class VerifierServiceTest {
mService = Mockito.mock(VerifierService.class, Answers.CALLS_REAL_METHODS);
mSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID,
TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO,
- new ArrayList<>(),
- new PersistableBundle(), null, null);
+ new ArrayList<>(), new PersistableBundle(), TEST_POLICY, null, null);
}
@Test
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 1c0073417c9a..6149382d0800 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -9,3 +9,6 @@ per-file PowerManager*.java = file:/services/core/java/com/android/server/power/
# PerformanceHintManager
per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
+
+# Caching
+per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 632721126714..ed9fc1c9e547 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -71,6 +71,7 @@ import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -82,6 +83,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -1628,6 +1630,42 @@ public class ViewRootImplTest {
});
}
+ @Test
+ @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE)
+ public void testAdjustAccessibilityFocusedBounds_largeEnoughBoundsAreUnchanged() {
+ final int strokeWidth = sContext.getSystemService(AccessibilityManager.class)
+ .getAccessibilityFocusStrokeWidth();
+ final int left, top, width, height;
+ left = top = 100;
+ width = height = strokeWidth * 2;
+ final Rect bounds = new Rect(left, top, left + width, top + height);
+ final Rect originalBounds = new Rect(bounds);
+
+ mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds);
+
+ assertThat(bounds).isEqualTo(originalBounds);
+ }
+
+ @Test
+ @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE)
+ public void testAdjustAccessibilityFocusedBounds_smallBoundsAreExpanded() {
+ final int strokeWidth = sContext.getSystemService(AccessibilityManager.class)
+ .getAccessibilityFocusStrokeWidth();
+ final int left, top, width, height;
+ left = top = 100;
+ width = height = strokeWidth;
+ final Rect bounds = new Rect(left, top, left + width, top + height);
+ final Rect originalBounds = new Rect(bounds);
+
+ mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds);
+
+ // Bounds should be centered on the same point, but expanded to at least strokeWidth * 2
+ assertThat(bounds.centerX()).isEqualTo(originalBounds.centerX());
+ assertThat(bounds.centerY()).isEqualTo(originalBounds.centerY());
+ assertThat(bounds.width()).isAtLeast(strokeWidth * 2);
+ assertThat(bounds.height()).isAtLeast(strokeWidth * 2);
+ }
+
private boolean setForceDarkSysProp(boolean isForceDarkEnabled) {
try {
SystemProperties.set(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2e72f0ec90b8..4aa7e96f352d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -572,6 +572,7 @@ applications that come with the platform
<permission name="android.permission.READ_BLOCKED_NUMBERS" />
<!-- Permission required for CTS test - PackageManagerTest -->
<permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
+ <permission name="android.permission.VERIFICATION_AGENT"/>
<!-- Permission required for CTS test CtsInputTestCases -->
<permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" />
<!-- Permission required for CTS test - PackageManagerShellCommandInstallTest -->
diff --git a/errorprone/OWNERS b/errorprone/OWNERS
index bddbdb364683..aa8c126a32e1 100644
--- a/errorprone/OWNERS
+++ b/errorprone/OWNERS
@@ -1,2 +1 @@
-jsharkey@android.com
-jsharkey@google.com
+colefaust@google.com
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 671eb6e514c5..ed17fdefcb53 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -26,6 +26,7 @@ import android.graphics.Typeface;
import android.graphics.fonts.Font;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import dalvik.annotation.optimization.CriticalNative;
@@ -132,6 +133,9 @@ public final class PositionedGlyphs {
@NonNull
public Font getFont(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ if (Flags.typefaceRedesign()) {
+ return mFonts.get(nGetFontId(mLayoutPtr, index));
+ }
return mFonts.get(index);
}
@@ -245,20 +249,29 @@ public final class PositionedGlyphs {
*/
public PositionedGlyphs(long layoutPtr, float xOffset, float yOffset) {
mLayoutPtr = layoutPtr;
- int glyphCount = nGetGlyphCount(layoutPtr);
- mFonts = new ArrayList<>(glyphCount);
mXOffset = xOffset;
mYOffset = yOffset;
- long prevPtr = 0;
- Font prevFont = null;
- for (int i = 0; i < glyphCount; ++i) {
- long ptr = nGetFont(layoutPtr, i);
- if (prevPtr != ptr) {
- prevPtr = ptr;
- prevFont = new Font(ptr);
+ if (Flags.typefaceRedesign()) {
+ int fontCount = nGetFontCount(layoutPtr);
+ mFonts = new ArrayList<>(fontCount);
+ for (int i = 0; i < fontCount; ++i) {
+ mFonts.add(new Font(nGetFontRef(layoutPtr, i)));
+ }
+ } else {
+ int glyphCount = nGetGlyphCount(layoutPtr);
+ mFonts = new ArrayList<>(glyphCount);
+
+ long prevPtr = 0;
+ Font prevFont = null;
+ for (int i = 0; i < glyphCount; ++i) {
+ long ptr = nGetFont(layoutPtr, i);
+ if (prevPtr != ptr) {
+ prevPtr = ptr;
+ prevFont = new Font(ptr);
+ }
+ mFonts.add(prevFont);
}
- mFonts.add(prevFont);
}
NoImagePreloadHolder.REGISTRY.registerNativeAllocation(this, layoutPtr);
@@ -290,6 +303,12 @@ public final class PositionedGlyphs {
private static native float nGetWeightOverride(long minikinLayout, int i);
@CriticalNative
private static native float nGetItalicOverride(long minikinLayout, int i);
+ @CriticalNative
+ private static native int nGetFontCount(long minikinLayout);
+ @CriticalNative
+ private static native long nGetFontRef(long minikinLayout, int fontId);
+ @CriticalNative
+ private static native int nGetFontId(long minikinLayout, int glyphIndex);
@Override
public boolean equals(Object o) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 882a8d035e93..ad194f707cf3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -1255,7 +1255,8 @@ class DividerPresenter implements View.OnTouchListener {
// Update divider line surface visibility and color.
// If a container is fully expanded, the divider line is invisible unless dragging.
- final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+ final boolean isDividerLineVisible = mProperties.mDividerWidthPx > 0
+ && (!mProperties.mIsDraggableExpandType || mIsDragging);
t.setVisibility(mDividerLineSurface, isDividerLineVisible);
t.setColor(mDividerLineSurface, colorToFloatArray(
Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
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 f181ce004478..fa9d2baa78d9 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.bar
import android.app.ActivityManager
import android.content.Context
+import android.content.pm.ShortcutInfo
import android.graphics.Insets
import android.graphics.Rect
import android.view.LayoutInflater
@@ -45,11 +46,14 @@ import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import java.util.Collections
import java.util.concurrent.Executor
@@ -72,14 +76,18 @@ class BubbleBarExpandedViewTest {
private lateinit var expandedViewManager: BubbleExpandedViewManager
private lateinit var positioner: BubblePositioner
private lateinit var bubbleTaskView: BubbleTaskView
+ private lateinit var bubble: Bubble
private lateinit var bubbleExpandedView: BubbleBarExpandedView
private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null
private var regionSamplingProvider: TestRegionSamplingProvider? = null
+ private val bubbleLogger = spy(BubbleLogger(UiEventLoggerFake()))
+
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
mainExecutor = TestExecutor()
bgExecutor = TestExecutor()
positioner = BubblePositioner(context, windowManager)
@@ -108,7 +116,7 @@ class BubbleBarExpandedViewTest {
bubbleExpandedView.initialize(
expandedViewManager,
positioner,
- BubbleLogger(UiEventLoggerFake()),
+ bubbleLogger,
false /* isOverflow */,
bubbleTaskView,
mainExecutor,
@@ -121,6 +129,20 @@ class BubbleBarExpandedViewTest {
// Helper should be created once attached to window
testableRegionSamplingHelper = regionSamplingProvider!!.helper
})
+
+ bubble = Bubble(
+ "key",
+ ShortcutInfo.Builder(context, "id").build(),
+ 100 /* desiredHeight */,
+ 0 /* desiredHeightResId */,
+ "title",
+ 0 /* taskId */,
+ null /* locus */,
+ true /* isDismissable */,
+ directExecutor(),
+ directExecutor()
+ ) {}
+ bubbleExpandedView.update(bubble)
}
@After
@@ -194,6 +216,16 @@ class BubbleBarExpandedViewTest {
assertThat(testableRegionSamplingHelper!!.isStopped).isTrue()
}
+ @Test
+ fun testEventLogging_dismissBubbleViaAppMenu() {
+ getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() }
+ val dismissMenuItem =
+ bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG)
+ assertThat(dismissMenuItem).isNotNull()
+ getInstrumentation().runOnMainSync { dismissMenuItem.performClick() }
+ verify(bubbleLogger).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU)
+ }
+
private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
val taskViewTaskController = mock<TaskViewTaskController>()
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 35ef2393bb9b..37596182f05b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -38,7 +38,7 @@
<Button
android:layout_width="94dp"
android:layout_height="60dp"
- android:id="@+id/maximize_menu_maximize_button"
+ android:id="@+id/maximize_menu_size_toggle_button"
style="?android:attr/buttonBarButtonStyle"
android:stateListAnimator="@null"
android:importantForAccessibility="yes"
@@ -48,7 +48,7 @@
android:alpha="0"/>
<TextView
- android:id="@+id/maximize_menu_maximize_window_text"
+ android:id="@+id/maximize_menu_size_toggle_button_text"
android:layout_width="94dp"
android:layout_height="18dp"
android:textSize="11sp"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1f1565160965..df1e2248872b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -492,8 +492,12 @@
<dimen name="desktop_mode_maximize_menu_buttons_outline_stroke">1dp</dimen>
<!-- The radius of the inner fill of the maximize menu buttons. -->
<dimen name="desktop_mode_maximize_menu_buttons_fill_radius">4dp</dimen>
- <!-- The padding between the outline and fill of the maximize menu buttons. -->
- <dimen name="desktop_mode_maximize_menu_buttons_fill_padding">4dp</dimen>
+ <!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. -->
+ <dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding">4dp</dimen>
+ <!-- The vertical padding between the outline and fill of the maximize menu restore button. -->
+ <dimen name="desktop_mode_maximize_menu_restore_button_fill_vertical_padding">13dp</dimen>
+ <!-- The horizontal padding between the outline and fill of the maximize menu restore button. -->
+ <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">21dp</dimen>
<!-- The corner radius of the maximize menu. -->
<dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 621e2aacd673..afac9f6433a3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -319,6 +319,8 @@
<string name="desktop_mode_non_resizable_snap_text">App can\'t be moved here</string>
<!-- Accessibility text for the Maximize Menu's maximize button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the Maximize Menu's restore button [CHAR LIMIT=NONE] -->
+ <string name="desktop_mode_maximize_menu_restore_button_text">Restore</string>
<!-- Accessibility text for the Maximize Menu's snap left button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
<!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
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 84405bbe5823..0ce651c3f1fe 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
@@ -252,6 +252,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
public void onDismissBubble(Bubble bubble) {
mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_GESTURE);
+ mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index 0300869cbbe1..52b807abddd6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles.bar;
import android.annotation.ColorInt;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -41,6 +42,9 @@ import java.util.ArrayList;
* Bubble bar expanded view menu
*/
public class BubbleBarMenuView extends LinearLayout {
+
+ public static final Object DISMISS_ACTION_TAG = new Object();
+
private ViewGroup mBubbleSectionView;
private ViewGroup mActionsSectionView;
private ImageView mBubbleIconView;
@@ -119,6 +123,9 @@ public class BubbleBarMenuView extends LinearLayout {
R.layout.bubble_bar_menu_item, mActionsSectionView, false);
itemView.update(action.mIcon, action.mTitle, action.mTint);
itemView.setOnClickListener(action.mOnClick);
+ if (action.mTag != null) {
+ itemView.setTag(action.mTag);
+ }
mActionsSectionView.addView(itemView);
}
}
@@ -159,6 +166,8 @@ public class BubbleBarMenuView extends LinearLayout {
private Icon mIcon;
private @ColorInt int mTint;
private String mTitle;
+ @Nullable
+ private Object mTag;
private OnClickListener mOnClick;
MenuAction(Icon icon, String title, OnClickListener onClick) {
@@ -171,5 +180,14 @@ public class BubbleBarMenuView extends LinearLayout {
this.mTint = tint;
this.mOnClick = onClick;
}
+
+ MenuAction(Icon icon, String title, @ColorInt int tint, @Nullable Object tag,
+ OnClickListener onClick) {
+ this.mIcon = icon;
+ this.mTitle = title;
+ this.mTint = tint;
+ this.mTag = tag;
+ this.mOnClick = onClick;
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 514810745e10..5ed01b66ec67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -212,6 +212,7 @@ class BubbleBarMenuViewController {
Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
resources.getString(R.string.bubble_dismiss_text),
tintColor,
+ BubbleBarMenuView.DISMISS_ACTION_TAG,
view -> {
hideMenu(true /* animated */);
if (mListener != null) {
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 b723337cc894..4440778a5a45 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
@@ -1185,7 +1185,13 @@ class DesktopTasksController(
val options = createNewWindowOptions(callingTask)
if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) {
wct.startTask(requestedTaskId, options.toBundle())
- transitions.startTransition(TRANSIT_OPEN, wct, null)
+ val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
+ callingTask.displayId, wct, requestedTaskId)
+ val runOnTransit = immersiveTransitionHandler
+ .exitImmersiveIfApplicable(wct, callingTask.displayId)
+ val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
+ addPendingMinimizeTransition(transition, taskToMinimize)
+ runOnTransit?.invoke(transition)
} else {
val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
splitScreenController.startTask(requestedTaskId, splitPosition,
@@ -1219,7 +1225,8 @@ class DesktopTasksController(
.determineNewInstancePosition(callingTaskInfo)
splitScreenController.startIntent(
launchIntent, context.userId, fillIn, splitPosition,
- options.toBundle(), null /* hideTaskToken */
+ options.toBundle(), null /* hideTaskToken */,
+ true /* forceLaunchNewTask */
)
}
WINDOWING_MODE_FREEFORM -> {
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 cbb08b804dfe..6da39951efbe 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
@@ -463,7 +463,7 @@ public class PipTransition extends PipTransitionController {
// so update fixed rotation state to default.
mFixedRotationState = FIXED_ROTATION_UNDEFINED;
- if (transition != mExitTransition) {
+ if (transition != mExitTransition && transition != mMoveToBackTransition) {
return;
}
// This means an expand happened before enter-pip finished and we are now "merging" a
@@ -477,8 +477,10 @@ public class PipTransition extends PipTransitionController {
cancelled = true;
}
- // Unset exitTransition AFTER cancel so that finishResize knows we are merging.
+ // Unset both exitTransition and moveToBackTransition AFTER cancel so that
+ // finishResize knows we are merging.
mExitTransition = null;
+ mMoveToBackTransition = null;
if (!cancelled) return;
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
@@ -515,7 +517,8 @@ public class PipTransition extends PipTransitionController {
// means we're expecting the exit transition will be "merged" into another transition
// (likely a remote like launcher), so don't fire the finish-callback here -- wait until
// the exit transition is merged.
- if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
+ if ((mExitTransition == null || mMoveToBackTransition == null || isAnimatingLocally())
+ && mFinishCallback != null) {
final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
final boolean hasValidLeash = leash != null && leash.isValid();
WindowContainerTransaction wct = null;
@@ -665,17 +668,6 @@ public class PipTransition extends PipTransitionController {
return null;
}
- @Nullable
- private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
- return change;
- }
- }
- return null;
- }
-
private void startExitAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 9b815817d4d3..94b344fb575a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -346,6 +347,21 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
+ /**
+ * Gets a change amongst the transition targets that is in a different final orientation than
+ * the display, signalling a potential fixed rotation transition.
+ */
+ @Nullable
+ public TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
+ return change;
+ }
+ }
+ return null;
+ }
+
/** End the currently-playing PiP animation. */
public void end() {
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index f40a87c39aef..fcd5c3baab5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -16,10 +16,14 @@
package com.android.wm.shell.pip2.animation;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.Surface;
@@ -60,6 +64,11 @@ public class PipEnterAnimator extends ValueAnimator
private final PointF mInitScale = new PointF();
private final PointF mInitPos = new PointF();
private final Rect mInitCrop = new Rect();
+ private final PointF mInitActivityScale = new PointF();
+ private final PointF mInitActivityPos = new PointF();
+
+ Matrix mTransformTensor = new Matrix();
+ final float[] mMatrixTmp = new float[9];
public PipEnterAnimator(Context context,
@NonNull SurfaceControl leash,
@@ -109,6 +118,10 @@ public class PipEnterAnimator extends ValueAnimator
@Override
public void onAnimationEnd(@NonNull Animator animation) {
+ if (mFinishTransaction != null) {
+ onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop,
+ 1f /* fraction */, mFinishTransaction);
+ }
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
}
@@ -126,16 +139,24 @@ public class PipEnterAnimator extends ValueAnimator
float fraction, SurfaceControl.Transaction tx) {
float scaleX = 1 + (initScale.x - 1) * (1 - fraction);
float scaleY = 1 + (initScale.y - 1) * (1 - fraction);
- tx.setScale(mLeash, scaleX, scaleY);
-
float posX = initPos.x + (mEndBounds.left - initPos.x) * fraction;
float posY = initPos.y + (mEndBounds.top - initPos.y) * fraction;
- tx.setPosition(mLeash, posX, posY);
+
+ int normalizedRotation = mRotation;
+ if (normalizedRotation == ROTATION_270) {
+ normalizedRotation = -ROTATION_90;
+ }
+ float degrees = -normalizedRotation * 90f * fraction;
Rect endCrop = new Rect(mEndBounds);
endCrop.offsetTo(0, 0);
mRectEvaluator.evaluate(fraction, initCrop, endCrop);
tx.setCrop(mLeash, mAnimatedRect);
+
+ mTransformTensor.setScale(scaleX, scaleY);
+ mTransformTensor.postTranslate(posX, posY);
+ mTransformTensor.postRotate(degrees);
+ tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp);
}
// no-ops
@@ -153,7 +174,22 @@ public class PipEnterAnimator extends ValueAnimator
* calculated differently from generic transitions.
* @param pipChange PiP change received as a transition target.
*/
- public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) {
+ public void setEnterStartState(@NonNull TransitionInfo.Change pipChange,
+ @NonNull TransitionInfo.Change pipActivityChange) {
+ PipUtils.calcEndTransform(pipActivityChange, pipChange, mInitActivityScale,
+ mInitActivityPos);
+ if (mStartTransaction != null && pipActivityChange.getLeash() != null) {
+ mStartTransaction.setCrop(pipActivityChange.getLeash(), null);
+ mStartTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x,
+ mInitActivityScale.y);
+ mStartTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x,
+ mInitActivityPos.y);
+ mFinishTransaction.setCrop(pipActivityChange.getLeash(), null);
+ mFinishTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x,
+ mInitActivityScale.y);
+ mFinishTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x,
+ mInitActivityPos.y);
+ }
PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 8fa5aa933929..a93ef12cb7fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -157,6 +157,7 @@ public class PipExpandAnimator extends ValueAnimator
.shadow(tx, mLeash, false /* applyCornerRadius */);
tx.apply();
}
+
private Rect getInsets(float fraction) {
final Rect startInsets = mSourceRectHintInsets;
final Rect endInsets = mZeroInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 73be8db0ea8a..0427294579dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -292,23 +292,34 @@ public class PipController implements ConfigurationChangeListener,
setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
if (!mPipTransitionState.isInPip()) {
+ // Skip the PiP-relevant updates if we aren't in a valid PiP state.
+ if (mPipTransitionState.isInFixedRotation()) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Fixed rotation flag shouldn't be set while in an invalid PiP state");
+ }
return;
}
mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
- // Update the caches to reflect the new display layout in the movement bounds;
- // temporarily update bounds to be at the top left for the movement bounds calculation.
- Rect toBounds = new Rect(0, 0,
- (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale),
- (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale));
- mPipBoundsState.setBounds(toBounds);
- mPipTouchHandler.updateMovementBounds();
-
- // The policy is to keep PiP snap fraction invariant.
- mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
- mPipBoundsState.setBounds(toBounds);
- t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ if (mPipTransitionState.isInFixedRotation()) {
+ // Do not change the bounds when in fixed rotation, but do update the movement bounds
+ // based on the current bounds state and potentially new display layout.
+ mPipTouchHandler.updateMovementBounds();
+ mPipTransitionState.setInFixedRotation(false);
+ } else {
+ Rect toBounds = new Rect(0, 0,
+ (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale),
+ (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale));
+ // Update the caches to reflect the new display layout in the movement bounds;
+ // temporarily update bounds to be at the top left for the movement bounds calculation.
+ mPipBoundsState.setBounds(toBounds);
+ mPipTouchHandler.updateMovementBounds();
+ // The policy is to keep PiP snap fraction invariant.
+ mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
+ mPipBoundsState.setBounds(toBounds);
+ }
+ t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds());
}
private void setDisplayLayout(DisplayLayout layout) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index a4a7973ef4bb..4d0432e1066e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -406,12 +406,9 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// We need to remove the callback even if the shelf is visible, in case it the delayed
// callback hasn't been executed yet to avoid the wrong final state.
mMainExecutor.removeCallbacks(mMoveOnShelVisibilityChanged);
- if (shelfVisible) {
- mMoveOnShelVisibilityChanged.run();
- } else {
- // Postpone moving in response to hide of Launcher in case there's another change
- mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY);
- }
+
+ // Postpone moving in response to hide of Launcher in case there's another change
+ mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ac1567aba6e9..779e4ea51347 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -16,7 +16,9 @@
package com.android.wm.shell.pip2.phone;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -36,6 +38,7 @@ import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -388,8 +391,15 @@ public class PipTransition extends PipTransitionController implements
return false;
}
- Rect startBounds = pipChange.getStartAbsBounds();
+ // We expect the PiP activity as a separate change in a config-at-end transition.
+ TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info,
+ pipChange.getTaskInfo().getToken());
+ if (pipActivityChange == null) {
+ return false;
+ }
+
Rect endBounds = pipChange.getEndAbsBounds();
+ Rect activityEndBounds = pipActivityChange.getEndAbsBounds();
SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
@@ -411,14 +421,63 @@ public class PipTransition extends PipTransitionController implements
}
}
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ int startRotation = pipChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
+ final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : startRotation - endRotation;
+
+ if (delta != ROTATION_0) {
+ mPipTransitionState.setInFixedRotation(true);
+ handleBoundsTypeFixedRotation(pipChange, pipActivityChange, fixedRotationChange);
+ }
+
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, sourceRectHint, Surface.ROTATION_0);
- animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
+ startTransaction, finishTransaction, endBounds, sourceRectHint, delta);
+ animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange,
+ pipActivityChange));
animator.setAnimationEndCallback(this::finishInner);
animator.start();
return true;
}
+ private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
+ TransitionInfo.Change pipActivityChange,
+ TransitionInfo.Change fixedRotationChange) {
+ final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
+ int startRotation = pipTaskChange.getStartRotation();
+ int endRotation = fixedRotationChange.getEndFixedRotation();
+
+ // Cache the task to activity offset to potentially restore later.
+ Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left,
+ endActivityBounds.top - endBounds.top);
+
+ // If we are running a fixed rotation bounds enter PiP animation,
+ // then update the display layout rotation, and recalculate the end rotation bounds.
+ // Update the endBounds in place, so that the PiP change is up-to-date.
+ mPipDisplayLayoutState.rotateTo(endRotation);
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mPipBoundsAlgorithm.getEntryDestinationBounds());
+ mPipBoundsAlgorithm.applySnapFraction(endBounds, snapFraction);
+ mPipBoundsState.setBounds(endBounds);
+
+ // Display bounds were already updated to represent the final orientation,
+ // so we just need to readjust the origin, and perform rotation about (0, 0).
+ boolean isClockwise = (endRotation - startRotation) == -ROTATION_270;
+ Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
+ int originTranslateX = isClockwise ? 0 : -displayBounds.width();
+ int originTranslateY = isClockwise ? -displayBounds.height() : 0;
+ endBounds.offset(originTranslateX, originTranslateY);
+
+ // Update the activity end bounds in place as well, as this is used for transform
+ // calculation later.
+ endActivityBounds.offsetTo(endBounds.left + activityEndOffset.x,
+ endBounds.top + activityEndOffset.y);
+ }
+
+
private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@@ -533,6 +592,19 @@ public class PipTransition extends PipTransitionController implements
}
@Nullable
+ private TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info,
+ @NonNull WindowContainerToken parent) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() == null
+ && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END)
+ && change.getParent() != null && change.getParent().equals(parent)) {
+ return change;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
private TransitionInfo.Change getChangeByToken(TransitionInfo info,
WindowContainerToken token) {
for (TransitionInfo.Change change : info.getChanges()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index a132796f4a84..ccdd66b5d1a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -155,6 +155,8 @@ public class PipTransitionState {
@Nullable
private Runnable mOnIdlePipTransitionStateRunnable;
+ private boolean mInFixedRotation = false;
+
/**
* An interface to track state updates as we progress through PiP transitions.
*/
@@ -256,7 +258,7 @@ public class PipTransitionState {
private void maybeRunOnIdlePipTransitionStateCallback() {
if (mOnIdlePipTransitionStateRunnable != null && isPipStateIdle()) {
- mOnIdlePipTransitionStateRunnable.run();
+ mMainHandler.post(mOnIdlePipTransitionStateRunnable);
mOnIdlePipTransitionStateRunnable = null;
}
}
@@ -303,6 +305,23 @@ public class PipTransitionState {
}
/**
+ * @return true if either in swipe or button-nav fixed rotation.
+ */
+ public boolean isInFixedRotation() {
+ return mInFixedRotation;
+ }
+
+ /**
+ * Sets the fixed rotation flag.
+ */
+ public void setInFixedRotation(boolean inFixedRotation) {
+ mInFixedRotation = inFixedRotation;
+ if (!inFixedRotation) {
+ maybeRunOnIdlePipTransitionStateCallback();
+ }
+ }
+
+ /**
* @return true if in swipe PiP to home. Note that this is true until overlay fades if used too.
*/
public boolean isInSwipePipToHomeTransition() {
@@ -351,7 +370,7 @@ public class PipTransitionState {
public boolean isPipStateIdle() {
// This needs to be a valid in-PiP state that isn't a transient state.
- return mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS;
+ return (mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS) && !isInFixedRotation();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 9e39f440915c..a23b576beebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -772,15 +772,25 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
instanceId);
}
+ @Override
+ public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
+ @SplitPosition int position, @Nullable Bundle options,
+ @Nullable WindowContainerToken hideTaskToken) {
+ startIntent(intent, userId1, fillInIntent, position, options, hideTaskToken,
+ false /* forceLaunchNewTask */);
+ }
+
/**
* Starts the given intent into split.
+ *
* @param hideTaskToken If non-null, a task matching this token will be moved to back in the
* same window container transaction as the starting of the intent.
+ * @param forceLaunchNewTask If true, this method will skip the check for a background task
+ * matching the intent and launch a new task.
*/
- @Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken) {
+ @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -798,8 +808,9 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
// To prevent accumulating large number of instances in the background, reuse task
// in the background. If we don't explicitly reuse, new may be created even if the app
// isn't multi-instance because WM won't automatically remove/reuse the previous instance
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
+ final ActivityManager.RecentTaskInfo taskInfo = forceLaunchNewTask ? null :
+ mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1,
hideTaskToken))
.orElse(null);
if (taskInfo != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index ae4bd1615ae1..6313231b449e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -244,8 +244,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
- taskInfo.isVisible && taskInfo.isVisibleRequested);
+ mVisible);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 0cb219ae4b81..3ae5a1afc7e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -62,6 +62,7 @@ import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.calculateMaximizeBounds
import com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_LINEAR_IN
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
@@ -73,7 +74,8 @@ import java.util.function.Supplier
/**
* Menu that appears when user long clicks the maximize button. Gives the user the option to
- * maximize the task or snap the task to the right or left half of the screen.
+ * maximize the task or restore previous task bounds from the maximized state and to snap the task
+ * to the right or left half of the screen.
*/
class MaximizeMenu(
private val syncQueue: SyncTransactionQueue,
@@ -176,6 +178,7 @@ class MaximizeMenu(
"MaximizeMenu")
maximizeMenuView = MaximizeMenuView(
context = decorWindowContext,
+ sizeToggleDirection = getSizeToggleDirection(),
menuHeight = menuHeight,
menuPadding = menuPadding,
).also { menuView ->
@@ -202,6 +205,18 @@ class MaximizeMenu(
}
}
+ private fun getSizeToggleDirection(): MaximizeMenuView.SizeToggleDirection {
+ val maximizeBounds = calculateMaximizeBounds(
+ displayController.getDisplayLayout(taskInfo.displayId)!!,
+ taskInfo
+ )
+ val maximized = taskInfo.configuration.windowConfiguration.bounds.equals(maximizeBounds)
+ return if (maximized)
+ MaximizeMenuView.SizeToggleDirection.RESTORE
+ else
+ MaximizeMenuView.SizeToggleDirection.MAXIMIZE
+ }
+
private fun loadDimensionPixelSize(resourceId: Int): Int {
return if (resourceId == Resources.ID_NULL) {
0
@@ -236,18 +251,19 @@ class MaximizeMenu(
* resizing a Task.
*/
class MaximizeMenuView(
- context: Context,
+ private val context: Context,
+ private val sizeToggleDirection: SizeToggleDirection,
private val menuHeight: Int,
- private val menuPadding: Int,
+ private val menuPadding: Int
) {
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup
private val container = requireViewById(R.id.container)
private val overlay = requireViewById(R.id.maximize_menu_overlay)
- private val maximizeText =
- requireViewById(R.id.maximize_menu_maximize_window_text) as TextView
- private val maximizeButton =
- requireViewById(R.id.maximize_menu_maximize_button) as Button
+ private val sizeToggleButtonText =
+ requireViewById(R.id.maximize_menu_size_toggle_button_text) as TextView
+ private val sizeToggleButton =
+ requireViewById(R.id.maximize_menu_size_toggle_button) as Button
private val snapWindowText =
requireViewById(R.id.maximize_menu_snap_window_text) as TextView
private val snapRightButton =
@@ -263,8 +279,6 @@ class MaximizeMenu(
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_outline_radius)
private val outlineStroke = context.resources
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_outline_stroke)
- private val fillPadding = context.resources
- .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_padding)
private val fillRadius = context.resources
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
@@ -324,7 +338,7 @@ class MaximizeMenu(
return@setOnHoverListener false
}
- maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() }
+ sizeToggleButton.setOnClickListener { onMaximizeClickListener?.invoke() }
snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() }
snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() }
rootView.setOnTouchListener { _, event ->
@@ -335,9 +349,17 @@ class MaximizeMenu(
true
}
+ val btnTextId = if (sizeToggleDirection == SizeToggleDirection.RESTORE)
+ R.string.desktop_mode_maximize_menu_restore_button_text
+ else
+ R.string.desktop_mode_maximize_menu_maximize_button_text
+ val btnText = context.resources.getText(btnTextId)
+ sizeToggleButton.contentDescription = btnText
+ sizeToggleButtonText.text = btnText
+
// To prevent aliasing.
- maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
- maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
/** Bind the menu views to the new [RunningTaskInfo] data. */
@@ -348,8 +370,8 @@ class MaximizeMenu(
rootView.background.setTint(style.backgroundColor)
// Maximize option.
- maximizeButton.background = style.maximizeOption.drawable
- maximizeText.setTextColor(style.textColor)
+ sizeToggleButton.background = style.maximizeOption.drawable
+ sizeToggleButtonText.setTextColor(style.textColor)
// Snap options.
snapWindowText.setTextColor(style.textColor)
@@ -358,8 +380,8 @@ class MaximizeMenu(
/** Animate the opening of the menu */
fun animateOpenMenu(onEnd: () -> Unit) {
- maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
- maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ sizeToggleButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ sizeToggleButtonText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
menuAnimatorSet = AnimatorSet()
menuAnimatorSet?.playTogether(
ObjectAnimator.ofFloat(rootView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
@@ -388,9 +410,9 @@ class MaximizeMenu(
// Scale up the children of the maximize menu so that the menu
// scale is cancelled out and only the background is scaled.
val value = animatedValue as Float
- maximizeButton.scaleY = value
+ sizeToggleButton.scaleY = value
snapButtonsLayout.scaleY = value
- maximizeText.scaleY = value
+ sizeToggleButtonText.scaleY = value
snapWindowText.scaleY = value
}
},
@@ -409,9 +431,9 @@ class MaximizeMenu(
startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
addUpdateListener {
val value = animatedValue as Float
- maximizeButton.alpha = value
+ sizeToggleButton.alpha = value
snapButtonsLayout.alpha = value
- maximizeText.alpha = value
+ sizeToggleButtonText.alpha = value
snapWindowText.alpha = value
}
},
@@ -423,8 +445,8 @@ class MaximizeMenu(
)
menuAnimatorSet?.addListener(
onEnd = {
- maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
- maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
onEnd.invoke()
}
)
@@ -433,8 +455,8 @@ class MaximizeMenu(
/** Animate the closing of the menu */
fun animateCloseMenu(onEnd: (() -> Unit)) {
- maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
- maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ sizeToggleButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ sizeToggleButtonText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
cancelAnimation()
menuAnimatorSet = AnimatorSet()
menuAnimatorSet?.playTogether(
@@ -464,9 +486,9 @@ class MaximizeMenu(
// Scale up the children of the maximize menu so that the menu
// scale is cancelled out and only the background is scaled.
val value = animatedValue as Float
- maximizeButton.scaleY = value
+ sizeToggleButton.scaleY = value
snapButtonsLayout.scaleY = value
- maximizeText.scaleY = value
+ sizeToggleButtonText.scaleY = value
snapWindowText.scaleY = value
}
},
@@ -485,9 +507,9 @@ class MaximizeMenu(
duration = ALPHA_ANIMATION_DURATION_MS
addUpdateListener {
val value = animatedValue as Float
- maximizeButton.alpha = value
+ sizeToggleButton.alpha = value
snapButtonsLayout.alpha = value
- maximizeText.alpha = value
+ sizeToggleButtonText.alpha = value
snapWindowText.alpha = value
}
},
@@ -498,8 +520,8 @@ class MaximizeMenu(
)
menuAnimatorSet?.addListener(
onEnd = {
- maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
- maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
onEnd?.invoke()
}
)
@@ -509,8 +531,8 @@ class MaximizeMenu(
/** Request that the accessibility service focus on the menu. */
fun requestAccessibilityFocus() {
// Focus the first button in the menu by default.
- maximizeButton.post {
- maximizeButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ sizeToggleButton.post {
+ sizeToggleButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
}
@@ -685,15 +707,31 @@ class MaximizeMenu(
paint.color = strokeAndFillColor
paint.style = Paint.Style.FILL
})
+
+ val (horizontalFillPadding, verticalFillPadding) =
+ if (sizeToggleDirection == SizeToggleDirection.MAXIMIZE) {
+ context.resources.getDimensionPixelSize(R.dimen
+ .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding) to
+ context.resources.getDimensionPixelSize(R.dimen
+ .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding)
+ } else {
+ context.resources.getDimensionPixelSize(R.dimen
+ .desktop_mode_maximize_menu_restore_button_fill_horizontal_padding) to
+ context.resources.getDimensionPixelSize(R.dimen
+ .desktop_mode_maximize_menu_restore_button_fill_vertical_padding)
+ }
+
return LayerDrawable(layers.toTypedArray()).apply {
when (numberOfLayers) {
3 -> {
setLayerInset(1, outlineStroke)
- setLayerInset(2, fillPadding)
+ setLayerInset(2, horizontalFillPadding, verticalFillPadding,
+ horizontalFillPadding, verticalFillPadding)
}
4 -> {
setLayerInset(intArrayOf(1, 2), outlineStroke)
- setLayerInset(3, fillPadding)
+ setLayerInset(3, horizontalFillPadding, verticalFillPadding,
+ horizontalFillPadding, verticalFillPadding)
}
else -> error("Unexpected number of layers: $numberOfLayers")
}
@@ -737,6 +775,11 @@ class MaximizeMenu(
enum class SnapToHalfSelection {
NONE, LEFT, RIGHT
}
+
+ /** The possible selection states of the size toggle button in the maximize menu. */
+ enum class SizeToggleDirection {
+ MAXIMIZE, RESTORE
+ }
}
companion object {
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 7640cb1fb616..b7ddfd1fc6eb 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
@@ -154,6 +154,7 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
+ // TODO(373638597) Add AppLayerIncreasesInSize assertion
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
)
@@ -208,7 +209,7 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ // TODO(373638597) Add AppLayerIncreasesInSize assertion
AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt
index 0b98ba2a9cd4..aa4f133123f5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt
@@ -24,7 +24,7 @@ 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.CORNER_RESIZE_TO_MAXIMUM_SIZE
-import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.scenarios.MaximiseAppWithCornerResize
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,7 +35,7 @@ import org.junit.runner.RunWith
* Assert that the maximum window size constraint is maintained.
*/
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class ResizeAppToMaximumWindowSizeLandscape : ResizeAppWithCornerResize(
+class ResizeAppToMaximumWindowSizeLandscape : MaximiseAppWithCornerResize(
rotation = Rotation.ROTATION_90
) {
@ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"])
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt
index b1c04d38a46c..e6b1ccf0111f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt
@@ -23,7 +23,7 @@ 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.CORNER_RESIZE_TO_MAXIMUM_SIZE
-import com.android.wm.shell.scenarios.ResizeAppWithCornerResize
+import com.android.wm.shell.scenarios.MaximiseAppWithCornerResize
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,7 +34,7 @@ import org.junit.runner.RunWith
* Assert that the maximum window size constraint is maintained.
*/
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class ResizeAppToMaximumWindowSizePortrait : ResizeAppWithCornerResize() {
+class ResizeAppToMaximumWindowSizePortrait : MaximiseAppWithCornerResize() {
@ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"])
@Test
override fun resizeAppWithCornerResizeToMaximumSize() =
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
new file mode 100644
index 000000000000..6637b01f9d9c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximiseAppWithCornerResize.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.AppProperty
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class MaximiseAppWithCornerResize(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ val appProperty: AppProperty = AppProperty.STANDARD
+) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val maxResizeChange = 3000
+ private val testApp =
+ DesktopModeAppHelper(
+ when (appProperty) {
+ AppProperty.STANDARD -> SimpleAppHelper(instrumentation)
+ AppProperty.NON_RESIZABLE -> NonResizeableAppHelper(instrumentation)
+ }
+ )
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ testApp.cornerResize(
+ wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.RIGHT_TOP,
+ maxResizeChange,
+ -maxResizeChange
+ )
+ }
+
+ @Test
+ open fun resizeAppWithCornerResizeToMaximumSize() {
+ testApp.cornerResize(
+ wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.LEFT_BOTTOM,
+ -maxResizeChange,
+ maxResizeChange
+ )
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index bd25639466a3..a7cebf402d8e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -19,11 +19,13 @@ package com.android.wm.shell.scenarios
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.AppProperty
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
@@ -63,6 +65,7 @@ abstract class ResizeAppWithCornerResize(
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
tapl.setEnableRotation(true)
+ ChangeDisplayOrientationRule.setRotation(rotation)
tapl.setExpectedRotation(rotation.value)
testApp.enterDesktopWithDrag(wmHelper, device)
}
@@ -78,34 +81,8 @@ abstract class ResizeAppWithCornerResize(
)
}
- @Test
- open fun resizeAppWithCornerResizeToMaximumSize() {
- val maxResizeChange = 3000
- testApp.cornerResize(
- wmHelper,
- device,
- DesktopModeAppHelper.Corners.RIGHT_TOP,
- maxResizeChange,
- -maxResizeChange
- )
- testApp.cornerResize(
- wmHelper,
- device,
- DesktopModeAppHelper.Corners.LEFT_BOTTOM,
- -maxResizeChange,
- maxResizeChange
- )
- }
-
@After
fun teardown() {
testApp.exit(wmHelper)
}
-
- companion object {
- enum class AppProperty {
- STANDARD,
- NON_RESIZABLE
- }
- }
}
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 b3c10d64c3a3..f9376570dc83 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
@@ -2902,7 +2902,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull())
+ optionsCaptor.capture(), anyOrNull(), eq(true)
+ )
assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
}
@@ -2917,7 +2918,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
verify(splitScreenController)
.startIntent(
any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull()
+ optionsCaptor.capture(), anyOrNull(), eq(true)
)
assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
@@ -2984,6 +2985,58 @@ class DesktopTasksControllerTest : ShellTestCase() {
.launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeform_minimizesIfNeeded() {
+ setUpLandscapeDisplay()
+ val homeTask = setUpHomeTask()
+ val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = getLatestWct(type = TRANSIT_OPEN)
+ // Home is moved to front of everything.
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.container == homeTask.token.asBinder() && hop.toTop
+ }
+ ).isTrue()
+ // And the oldest task isn't moved in front of home, effectively minimizing it.
+ assertThat(
+ wct.hierarchyOps.none { hop ->
+ hop.container == oldestTask.token.asBinder() && hop.toTop
+ }
+ ).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val immersiveTask = setUpFreeformTask()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = immersiveTask.displayId,
+ taskId = immersiveTask.taskId,
+ immersive = true
+ )
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(mockDesktopFullImmersiveTransitionHandler
+ .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId))).thenReturn(runOnStartTransit)
+
+ runOpenInstance(immersiveTask, freeformTask.taskId)
+
+ verify(mockDesktopFullImmersiveTransitionHandler)
+ .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId))
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
private fun runOpenInstance(
callingTask: RunningTaskInfo,
requestedTaskId: Int
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 9260a07fd945..ef3af8e7bdac 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -297,6 +297,28 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
+ public void startIntent_forceLaunchNewTaskTrue_skipsBackgroundTasks() {
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ true /* forceLaunchNewTask */);
+ verify(mRecentTasks, never()).findTaskInBackground(any(), anyInt(), any());
+ }
+
+ @Test
+ public void startIntent_forceLaunchNewTaskFalse_checksBackgroundTasks() {
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ false /* forceLaunchNewTask */);
+ verify(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
+ }
+
+ @Test
public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
final String reason = "test";
when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index e9845c1d9f13..27817e9eb984 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -4,7 +4,6 @@ package com.google.android.appfunctions.sidecar {
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
@@ -15,9 +14,7 @@ package com.google.android.appfunctions.sidecar {
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
- method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index d660926575d1..43377d8eb91c 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -126,33 +126,6 @@ public final class AppFunctionManager {
}
/**
- * Executes the app function.
- *
- * <p>Proxies request and response to the underlying {@link
- * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and
- * response in the appropriate type required by the function.
- *
- * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor,
- * CancellationSignal, Consumer)} instead. This method will be removed once usage references
- * are updated.
- */
- @Deprecated
- public void executeAppFunction(
- @NonNull ExecuteAppFunctionRequest sidecarRequest,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- Objects.requireNonNull(sidecarRequest);
- Objects.requireNonNull(executor);
- Objects.requireNonNull(callback);
-
- executeAppFunction(
- sidecarRequest,
- executor,
- new CancellationSignal(),
- callback);
- }
-
- /**
* Returns a boolean through a callback, indicating whether the app function is enabled.
*
* <p>* This method can only check app functions owned by the caller, or those where the caller
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 2a168e871713..0dc87e45b7e3 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -119,76 +119,9 @@ public abstract class AppFunctionService extends Service {
* @param callback A callback to report back the result.
*/
@MainThread
- public void onExecuteFunction(
+ public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- onExecuteFunction(request, cancellationSignal, callback);
- }
-
- /**
- * Called by the system to execute a specific app function.
- *
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
- *
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
- *
- * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
- * thread and dispatch the result with the given callback. You should always report back the
- * result using the callback, no matter if the execution was successful or not.
- *
- * @param request The function execution request.
- * @param cancellationSignal A {@link CancellationSignal} to cancel the request.
- * @param callback A callback to report back the result.
- * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String,
- * CancellationSignal, Consumer)} instead. This method will be removed once usage references
- * are updated.
- */
- @MainThread
- @Deprecated
- public void onExecuteFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull CancellationSignal cancellationSignal,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- onExecuteFunction(request, callback);
- }
-
- /**
- * Called by the system to execute a specific app function.
- *
- * <p>This method is triggered when the system requests your AppFunctionService to handle a
- * particular function you have registered and made available.
- *
- * <p>To ensure proper routing of function requests, assign a unique identifier to each
- * function. This identifier doesn't need to be globally unique, but it must be unique within
- * your app. For example, a function to order food could be identified as "orderFood". In most
- * cases this identifier should come from the ID automatically generated by the AppFunctions
- * SDK. You can determine the specific function to invoke by calling {@link
- * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
- *
- * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
- * thread and dispatch the result with the given callback. You should always report back the
- * result using the callback, no matter if the execution was successful or not.
- *
- * @param request The function execution request.
- * @param callback A callback to report back the result.
- * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
- * Consumer)} instead. This method will be removed once usage references are updated.
- */
- @MainThread
- @Deprecated
- public void onExecuteFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
- Log.w(
- "AppFunctionService",
- "Calling deprecated default implementation of onExecuteFunction");
- }
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
}
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 456338631ae4..70e6beda6cb9 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -31,12 +31,34 @@
namespace android {
+struct FakedFontKey {
+ uint32_t operator()(const minikin::FakedFont& fakedFont) const {
+ return minikin::Hasher()
+ .update(reinterpret_cast<uintptr_t>(fakedFont.font.get()))
+ .update(fakedFont.fakery.bits())
+ .update(fakedFont.fakery.variationSettings())
+ .hash();
+ }
+};
+
struct LayoutWrapper {
LayoutWrapper(minikin::Layout&& layout, float ascent, float descent)
: layout(std::move(layout)), ascent(ascent), descent(descent) {}
+
+ LayoutWrapper(minikin::Layout&& layout, float ascent, float descent, std::vector<jlong>&& fonts,
+ std::vector<uint32_t>&& fontIds)
+ : layout(std::move(layout))
+ , ascent(ascent)
+ , descent(descent)
+ , fonts(std::move(fonts))
+ , fontIds(std::move(fontIds)) {}
+
minikin::Layout layout;
float ascent;
float descent;
+
+ std::vector<jlong> fonts;
+ std::vector<uint32_t> fontIds; // per glyph
};
static void releaseLayout(jlong ptr) {
@@ -64,6 +86,43 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou
overallDescent = std::max(overallDescent, extent.descent);
}
+ if (text_feature::typeface_redesign()) {
+ uint32_t runCount = layout.getFontRunCount();
+
+ std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds;
+ std::vector<jlong> fonts;
+ std::vector<uint32_t> fontIds;
+
+ fontIds.resize(layout.nGlyphs());
+ for (uint32_t ri = 0; ri < runCount; ++ri) {
+ const minikin::FakedFont& fakedFont = layout.getFontRunFont(ri);
+
+ auto it = fakedToFontIds.find(fakedFont);
+ uint32_t fontId;
+ if (it != fakedToFontIds.end()) {
+ fontId = it->second; // We've seen it.
+ } else {
+ fontId = fonts.size(); // This is new to us. Create new one.
+ std::shared_ptr<minikin::Font> font = std::make_shared<minikin::Font>(
+ fakedFont.font, fakedFont.fakery.variationSettings());
+ fonts.push_back(reinterpret_cast<jlong>(new FontWrapper(std::move(font))));
+ fakedToFontIds.insert(std::make_pair(fakedFont, fontId));
+ }
+
+ const uint32_t runStart = layout.getFontRunStart(ri);
+ const uint32_t runEnd = layout.getFontRunEnd(ri);
+ for (uint32_t i = runStart; i < runEnd; ++i) {
+ fontIds[i] = fontId;
+ }
+ }
+
+ std::unique_ptr<LayoutWrapper> ptr =
+ std::make_unique<LayoutWrapper>(std::move(layout), overallAscent, overallDescent,
+ std::move(fonts), std::move(fontIds));
+
+ return reinterpret_cast<jlong>(ptr.release());
+ }
+
std::unique_ptr<LayoutWrapper> ptr = std::make_unique<LayoutWrapper>(
std::move(layout), overallAscent, overallDescent
);
@@ -156,6 +215,8 @@ static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong
return layout->layout.getFakery(i).isFakeItalic();
}
+constexpr float NO_OVERRIDE = -1;
+
float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) {
for (const minikin::FontVariation& fv : fakery.variationSettings()) {
if (fv.axisTag == tag) {
@@ -171,12 +232,7 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon
if (text_feature::typeface_redesign()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
- if (!std::isnan(value)) {
- return value;
- } else {
- const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i);
- return font->style().weight();
- }
+ return std::isnan(value) ? NO_OVERRIDE : value;
} else {
return layout->layout.getFakery(i).wghtAdjustment();
}
@@ -188,12 +244,7 @@ static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlon
if (text_feature::typeface_redesign()) {
float value =
findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
- if (!std::isnan(value)) {
- return value;
- } else {
- const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i);
- return font->style().isItalic();
- }
+ return std::isnan(value) ? NO_OVERRIDE : value;
} else {
return layout->layout.getFakery(i).italAdjustment();
}
@@ -207,6 +258,24 @@ static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint
}
// CriticalNative
+static jint TextShaper_Result_getFontCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->fonts.size();
+}
+
+// CriticalNative
+static jlong TextShaper_Result_getFontRef(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint fontId) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->fonts[fontId];
+}
+
+// CriticalNative
+static jint TextShaper_Result_getFontId(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint glyphIdx) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->fontIds[glyphIdx];
+}
+
+// CriticalNative
static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) {
return reinterpret_cast<jlong>(releaseLayout);
}
@@ -250,6 +319,10 @@ static const JNINativeMethod gResultMethods[] = {
{"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
{"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
{"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
+
+ {"nGetFontCount", "(J)I", (void*)TextShaper_Result_getFontCount},
+ {"nGetFontRef", "(JI)J", (void*)TextShaper_Result_getFontRef},
+ {"nGetFontId", "(JI)I", (void*)TextShaper_Result_getFontId},
};
int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index eeb4853afadc..961962f6a010 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -85,8 +85,7 @@ interface IMediaRouterService {
void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState);
void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
- in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
- in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName);
+ in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
String sessionId, in MediaRoute2Info route);
void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b84990b54bd5..3499c438086d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2770,7 +2770,7 @@ public final class MediaRouter2 {
|| isSystemRouteReselection) {
transferToRoute(sessionInfo, route, mClientUser, mClientPackageName);
} else {
- requestCreateSession(sessionInfo, route, mClientUser, mClientPackageName);
+ requestCreateSession(sessionInfo, route);
}
}
@@ -2826,10 +2826,7 @@ public final class MediaRouter2 {
* @param route The {@link MediaRoute2Info route} to transfer to.
*/
private void requestCreateSession(
- @NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiatorPackageName) {
+ @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
this.onTransferFailed(oldSession, route);
@@ -2840,12 +2837,7 @@ public final class MediaRouter2 {
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient,
- requestId,
- oldSession,
- route,
- transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ mClient, requestId, oldSession, route);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 8fa0e49e8b96..7e1dccf2d366 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -524,8 +524,7 @@ public final class MediaRouter2Manager {
transferToRoute(
sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName);
} else {
- requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ requestCreateSession(sessionInfo, route);
}
}
@@ -914,9 +913,7 @@ public final class MediaRouter2Manager {
}
}
- private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiationPackageName) {
+ private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) {
if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
notifyTransferFailed(oldSession, route);
@@ -927,8 +924,7 @@ public final class MediaRouter2Manager {
try {
mMediaRouterService.requestCreateSessionWithManager(
- mClient, requestId, oldSession, route, transferInitiatorUserHandle,
- transferInitiationPackageName);
+ mClient, requestId, oldSession, route);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
index 33519cba2940..dd7eac776583 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
@@ -26,12 +26,12 @@
android:outlineAmbientShadowColor="@android:color/transparent"
android:outlineSpotShadowColor="@android:color/transparent"
android:background="@android:color/transparent"
- android:theme="@style/Theme.CollapsingToolbar.Settings">
+ android:theme="@style/ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar">
<Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
+ android:layout_height="?android:attr/actionBarSize"
android:theme="?android:attr/actionBarTheme"
android:transitionName="shared_element_view"
app:layout_collapseMode="pin"/>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
index c20beaf9bf93..02f171cf0d9e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
@@ -21,4 +21,15 @@
<item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item>
<item name="colorAccent">@color/settingslib_accent_device_default_dark</item>
</style>
-</resources> \ No newline at end of file
+
+ <!--
+ ~ TODO(b/349675008): Remove this theme overlay once the platform bridge theme properly sets
+ ~ the MaterialComponents colors based on the platform theme.
+ -->
+ <style name="ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_dark</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
index 9ecc297c6d36..403931764d7e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
@@ -21,4 +21,15 @@
<item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item>
<item name="colorAccent">@color/settingslib_accent_device_default_light</item>
</style>
-</resources> \ No newline at end of file
+
+ <!--
+ ~ TODO(b/349675008): Remove this theme overlay once the platform bridge theme properly sets
+ ~ the MaterialComponents colors based on the platform theme.
+ -->
+ <style name="ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_light</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml
new file mode 100644
index 000000000000..bcb9baf94706
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml
@@ -0,0 +1,176 @@
+<?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.
+-->
+
+<!-- See appcompat/appcompat/THEMES for the theme structure. -->
+<resources>
+ <!--
+ ~ Bridge theme overlay to simulate AppCompat themes based on a platform theme.
+ ~ Only non-widget attributes are included here since we should still use the platform widgets.
+ ~ Only public theme attributes (as in platform public-final.xml) can be referenced here since
+ ~ this is used in modules.
+ -->
+ <style name="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" parent="">
+ <!-- START Base.V7.Theme.AppCompat -->
+
+ <item name="colorBackgroundFloating">?android:colorBackgroundFloating</item>
+
+ <item name="isLightTheme">?android:isLightTheme</item>
+
+ <item name="selectableItemBackground">?android:selectableItemBackground</item>
+ <item name="selectableItemBackgroundBorderless">?android:selectableItemBackgroundBorderless</item>
+ <item name="homeAsUpIndicator">?android:homeAsUpIndicator</item>
+
+ <item name="dividerVertical">?android:dividerVertical</item>
+ <item name="dividerHorizontal">?android:dividerHorizontal</item>
+
+ <!-- List attributes -->
+ <item name="textAppearanceListItem">?android:textAppearanceListItem</item>
+ <item name="textAppearanceListItemSmall">?android:textAppearanceListItemSmall</item>
+ <item name="textAppearanceListItemSecondary">?android:textAppearanceListItemSecondary</item>
+ <item name="listPreferredItemHeight">?android:listPreferredItemHeight</item>
+ <item name="listPreferredItemHeightSmall">?android:listPreferredItemHeightSmall</item>
+ <item name="listPreferredItemHeightLarge">?android:listPreferredItemHeightLarge</item>
+ <item name="listPreferredItemPaddingLeft">?android:listPreferredItemPaddingLeft</item>
+ <item name="listPreferredItemPaddingRight">?android:listPreferredItemPaddingRight</item>
+ <item name="listPreferredItemPaddingStart">?android:listPreferredItemPaddingStart</item>
+ <item name="listPreferredItemPaddingEnd">?android:listPreferredItemPaddingEnd</item>
+
+ <!-- Color palette -->
+ <item name="colorPrimaryDark">?android:colorPrimaryDark</item>
+ <item name="colorPrimary">?android:colorPrimary</item>
+ <item name="colorAccent">?android:colorAccent</item>
+
+ <item name="colorControlNormal">?android:colorControlNormal</item>
+ <item name="colorControlActivated">?android:colorControlActivated</item>
+ <item name="colorControlHighlight">?android:colorControlHighlight</item>
+ <item name="colorButtonNormal">?android:colorButtonNormal</item>
+
+ <item name="colorError">?android:colorError</item>
+
+ <!-- END Base.V7.Theme.AppCompat -->
+ </style>
+ <style name="Base.ThemeOverlay.AppCompat.PlatformBridge" parent="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" />
+ <style name="ThemeOverlay.AppCompat.PlatformBridge" parent="Base.ThemeOverlay.AppCompat.PlatformBridge" />
+
+ <!--
+ ~ Bridge theme overlay to simulate MaterialComponents themes based on a platform theme.
+ -->
+ <style name="Base.V31.ThemeOverlay.MaterialComponents.PlatformBridge" parent="ThemeOverlay.AppCompat.PlatformBridge">
+ <!-- START Base.V14.Theme.MaterialComponents.Bridge -->
+ <!--
+ ~ This is copied as-is from the original bridge theme since it is guaranteed to not affect
+ ~ existing widgets.
+ -->
+
+ <item name="isMaterialTheme">true</item>
+
+ <item name="colorPrimaryVariant">@color/design_dark_default_color_primary_variant</item>
+ <item name="colorSecondary">@color/design_dark_default_color_secondary</item>
+ <item name="colorSecondaryVariant">@color/design_dark_default_color_secondary_variant</item>
+ <item name="colorSurface">@color/design_dark_default_color_surface</item>
+ <item name="colorPrimarySurface">?attr/colorSurface</item>
+ <item name="colorOnPrimary">@color/design_dark_default_color_on_primary</item>
+ <item name="colorOnSecondary">@color/design_dark_default_color_on_secondary</item>
+ <item name="colorOnBackground">@color/design_dark_default_color_on_background</item>
+ <item name="colorOnError">@color/design_dark_default_color_on_error</item>
+ <item name="colorOnSurface">@color/design_dark_default_color_on_surface</item>
+ <item name="colorOnPrimarySurface">?attr/colorOnSurface</item>
+
+ <item name="scrimBackground">@color/mtrl_scrim_color</item>
+ <item name="popupMenuBackground">@drawable/mtrl_popupmenu_background_overlay</item>
+
+ <item name="minTouchTargetSize">@dimen/mtrl_min_touch_target_size</item>
+
+ <!-- MaterialComponents Widget styles -->
+ <item name="badgeStyle">@style/Widget.MaterialComponents.Badge</item>
+ <item name="bottomAppBarStyle">@style/Widget.MaterialComponents.BottomAppBar</item>
+ <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item>
+ <item name="chipGroupStyle">@style/Widget.MaterialComponents.ChipGroup</item>
+ <item name="chipStandaloneStyle">@style/Widget.MaterialComponents.Chip.Entry</item>
+ <item name="circularProgressIndicatorStyle">@style/Widget.MaterialComponents.CircularProgressIndicator</item>
+ <item name="extendedFloatingActionButtonStyle">@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon</item>
+ <item name="linearProgressIndicatorStyle">@style/Widget.MaterialComponents.LinearProgressIndicator</item>
+ <item name="materialButtonStyle">@style/Widget.MaterialComponents.Button</item>
+ <item name="materialButtonOutlinedStyle">@style/Widget.MaterialComponents.Button.OutlinedButton</item>
+ <item name="materialButtonToggleGroupStyle">@style/Widget.MaterialComponents.MaterialButtonToggleGroup</item>
+ <item name="materialCardViewStyle">@style/Widget.MaterialComponents.CardView</item>
+ <item name="navigationRailStyle">@style/Widget.MaterialComponents.NavigationRailView</item>
+ <item name="sliderStyle">@style/Widget.MaterialComponents.Slider</item>
+
+ <!-- Type styles -->
+ <item name="textAppearanceHeadline1">@style/TextAppearance.MaterialComponents.Headline1</item>
+ <item name="textAppearanceHeadline2">@style/TextAppearance.MaterialComponents.Headline2</item>
+ <item name="textAppearanceHeadline3">@style/TextAppearance.MaterialComponents.Headline3</item>
+ <item name="textAppearanceHeadline4">@style/TextAppearance.MaterialComponents.Headline4</item>
+ <item name="textAppearanceHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
+ <item name="textAppearanceHeadline6">@style/TextAppearance.MaterialComponents.Headline6</item>
+ <item name="textAppearanceSubtitle1">@style/TextAppearance.MaterialComponents.Subtitle1</item>
+ <item name="textAppearanceSubtitle2">@style/TextAppearance.MaterialComponents.Subtitle2</item>
+ <item name="textAppearanceBody1">@style/TextAppearance.MaterialComponents.Body1</item>
+ <item name="textAppearanceBody2">@style/TextAppearance.MaterialComponents.Body2</item>
+ <item name="textAppearanceCaption">@style/TextAppearance.MaterialComponents.Caption</item>
+ <item name="textAppearanceButton">@style/TextAppearance.MaterialComponents.Button</item>
+ <item name="textAppearanceOverline">@style/TextAppearance.MaterialComponents.Overline</item>
+
+ <!-- Shape styles -->
+ <item name="shapeAppearanceSmallComponent">
+ @style/ShapeAppearance.MaterialComponents.SmallComponent
+ </item>
+ <item name="shapeAppearanceMediumComponent">
+ @style/ShapeAppearance.MaterialComponents.MediumComponent
+ </item>
+ <item name="shapeAppearanceLargeComponent">
+ @style/ShapeAppearance.MaterialComponents.LargeComponent
+ </item>
+
+ <!-- Motion -->
+ <item name="motionEasingStandard">@string/material_motion_easing_standard</item>
+ <item name="motionEasingEmphasized">@string/material_motion_easing_emphasized</item>
+ <item name="motionEasingDecelerated">@string/material_motion_easing_decelerated</item>
+ <item name="motionEasingAccelerated">@string/material_motion_easing_accelerated</item>
+ <item name="motionEasingLinear">@string/material_motion_easing_linear</item>
+
+ <item name="motionDurationShort1">@integer/material_motion_duration_short_1</item>
+ <item name="motionDurationShort2">@integer/material_motion_duration_short_2</item>
+ <item name="motionDurationMedium1">@integer/material_motion_duration_medium_1</item>
+ <item name="motionDurationMedium2">@integer/material_motion_duration_medium_2</item>
+ <item name="motionDurationLong1">@integer/material_motion_duration_long_1</item>
+ <item name="motionDurationLong2">@integer/material_motion_duration_long_2</item>
+
+ <item name="motionPath">@integer/material_motion_path</item>
+
+ <!-- Elevation Overlays -->
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorOnSurface</item>
+
+ <!-- END Base.V14.Theme.MaterialComponents.Bridge -->
+
+ <!-- START Base.V14.Theme.MaterialComponents -->
+ <!--
+ ~ Only a subset of widget attributes being actually used are included here since there are
+ ~ too many of them and they need to be investigated on a case-by-case basis.
+ -->
+
+ <!-- Framework, AppCompat, or Design Widget styles -->
+ <item name="appBarLayoutStyle">@style/Widget.MaterialComponents.AppBarLayout.Surface</item>
+
+ <!-- END Base.V14.Theme.MaterialComponents -->
+ </style>
+ <style name="Base.ThemeOverlay.MaterialComponents.PlatformBridge" parent="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" />
+ <style name="ThemeOverlay.MaterialComponents.PlatformBridge" parent="Base.ThemeOverlay.AppCompat.PlatformBridge" />
+</resources>
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
index fdefa39dbd2e..1b270deae8de 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
@@ -99,6 +99,37 @@ abstract class SettingsStore(protected val contentResolver: ContentResolver) :
contentResolver.unregisterContentObserver(contentObserver)
}
+ /** Gets the boolean value of given key. */
+ fun getBoolean(key: String): Boolean? = getValue(key, Boolean::class.javaObjectType)
+
+ /** Sets boolean value for given key, null value means delete the key from data store. */
+ fun setBoolean(key: String, value: Boolean?) =
+ setValue(key, Boolean::class.javaObjectType, value)
+
+ /** Gets the float value of given key. */
+ fun getFloat(key: String): Float? = getValue(key, Float::class.javaObjectType)
+
+ /** Sets float value for given key, null value means delete the key from data store. */
+ fun setFloat(key: String, value: Float?) = setValue(key, Float::class.javaObjectType, value)
+
+ /** Gets the int value of given key. */
+ fun getInt(key: String): Int? = getValue(key, Int::class.javaObjectType)
+
+ /** Sets int value for given key, null value means delete the key from data store. */
+ fun setInt(key: String, value: Int?) = setValue(key, Int::class.javaObjectType, value)
+
+ /** Gets the long value of given key. */
+ fun getLong(key: String): Long? = getValue(key, Long::class.javaObjectType)
+
+ /** Sets long value for given key, null value means delete the key from data store. */
+ fun setLong(key: String, value: Long?) = setValue(key, Long::class.javaObjectType, value)
+
+ /** Gets the string value of given key. */
+ fun getString(key: String): String? = getValue(key, String::class.javaObjectType)
+
+ /** Sets string value for given key, null value means delete the key from data store. */
+ fun setString(key: String, value: String?) = setValue(key, String::class.javaObjectType, value)
+
/** Tag for logging. */
abstract val tag: String
}
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
index 1412c84c137b..89881f4d74bb 100644
--- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
@@ -37,7 +37,7 @@ import org.junit.runner.RunWith
abstract class CatalystScreenTestCase {
@get:Rule val setFlagsRule = SetFlagsRule()
- protected val context: Context = ApplicationProvider.getApplicationContext()
+ protected val appContext: Context = ApplicationProvider.getApplicationContext()
/** Catalyst screen. */
protected abstract val preferenceScreenCreator: PreferenceScreenCreator
@@ -52,12 +52,12 @@ abstract class CatalystScreenTestCase {
@Test
open fun migration() {
enableCatalystScreen()
- assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
+ assertThat(preferenceScreenCreator.isFlagEnabled(appContext)).isTrue()
val catalystScreen = dumpPreferenceScreen()
Log.i(TAG, catalystScreen)
disableCatalystScreen()
- assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
+ assertThat(preferenceScreenCreator.isFlagEnabled(appContext)).isFalse()
val legacyScreen = dumpPreferenceScreen()
assertThat(catalystScreen).isEqualTo(legacyScreen)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index dfd296fe006f..8636524ed23c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -23,6 +23,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
+import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
@@ -109,6 +110,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
TopIntroPreferencePageProvider,
CheckBoxPreferencePageProvider,
TwoTargetButtonPreferencePageProvider,
+ CardPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
new file mode 100644
index 000000000000..5659e2f33156
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.card
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Stars
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.card.SuggestionCard
+import com.android.settingslib.spa.widget.card.SuggestionCardModel
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+object CardPageProvider : SettingsPageProvider {
+ override val name = "Card"
+
+ override fun getTitle(arguments: Bundle?) = TITLE
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ SuggestionCard()
+ SuggestionCardWithLongTitle()
+ SuggestionCardDismissible()
+ }
+ }
+
+ @Composable
+ private fun SuggestionCard() {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Filled.Stars,
+ )
+ )
+ }
+
+ @Composable
+ private fun SuggestionCardWithLongTitle() {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Top level suggestion card with a really, really long title",
+ imageVector = Icons.Filled.Stars,
+ onClick = {},
+ )
+ )
+ }
+
+ @Composable
+ private fun SuggestionCardDismissible() {
+ var isVisible by rememberSaveable { mutableStateOf(true) }
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Filled.Stars,
+ onDismiss = { isVisible = false },
+ isVisible = isVisible,
+ )
+ )
+ }
+
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+
+ private const val TITLE = "Sample Card"
+}
+
+@Preview
+@Composable
+private fun CardPagePreview() {
+ SettingsTheme { CardPageProvider.Page(null) }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 4d77ea173a85..ebfc0c536868 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -27,6 +27,7 @@ import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.card.CardPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
@@ -80,6 +81,7 @@ object HomePageProvider : SettingsPageProvider {
DialogMainPageProvider.Entry()
EditorMainPageProvider.Entry()
BannerPageProvider.Entry()
+ CardPageProvider.Entry()
CopyablePageProvider.Entry()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 395748384b85..08bedf99519d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -28,6 +28,7 @@ object SettingsDimension {
val paddingExtraSmall6 = 12.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
+ val paddingExtraLarge1 = 28.dp
val spinnerHorizontalPadding = paddingExtraLarge
val spinnerVerticalPadding = paddingLarge
@@ -37,6 +38,7 @@ object SettingsDimension {
val actionIconPadding = 4.dp
val itemIconSize = 24.dp
+ val itemIconContainerSizeSmall = 40.dp
val itemIconContainerSize = 72.dp
val itemPaddingStart = if (isSpaExpressiveEnabled) paddingLarge else paddingExtraLarge
val itemPaddingEnd = paddingLarge
@@ -47,6 +49,12 @@ object SettingsDimension {
end = itemPaddingEnd,
bottom = itemPaddingVertical,
)
+ val footerItemPadding = PaddingValues(
+ start = paddingExtraLarge1,
+ top = itemPaddingVertical,
+ end = itemPaddingEnd,
+ bottom = itemPaddingVertical,
+ )
val textFieldPadding = PaddingValues(
start = itemPaddingStart,
end = itemPaddingEnd,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 86ba6864574c..61607bc8ae8a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -29,4 +29,6 @@ object SettingsShape {
val CornerLarge = RoundedCornerShape(24.dp)
val CornerExtraLarge = RoundedCornerShape(28.dp)
+
+ val CornerExtraLarge1 = RoundedCornerShape(40.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt
new file mode 100644
index 000000000000..2126634ebd4d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.settingslib.spa.widget.card
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Stars
+import androidx.compose.material.icons.outlined.Stars
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
+
+data class SuggestionCardModel(
+ val title: String,
+ val description: String? = null,
+ val imageVector: ImageVector,
+
+ /**
+ * A dismiss button will be displayed if this is not null.
+ *
+ * And this callback will be called when user clicks the button.
+ */
+ val onDismiss: (() -> Unit)? = null,
+ val isVisible: Boolean = true,
+ val onClick: (() -> Unit)? = null,
+)
+
+@Composable
+fun SuggestionCard(model: SuggestionCardModel) {
+ AnimatedVisibility(visible = model.isVisible) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ .fillMaxWidth()
+ .heightIn(min = SettingsDimension.preferenceMinHeight)
+ .clip(SettingsShape.CornerExtraLarge1)
+ .background(MaterialTheme.colorScheme.secondaryContainer)
+ .then(model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier)
+ .padding(SettingsDimension.paddingExtraSmall6),
+ ) {
+ SuggestionCardIcon(model.imageVector)
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ Column(modifier = Modifier.weight(1f).semantics(mergeDescendants = true) {}) {
+ SuggestionCardTitle(model.title)
+ if (model.description != null) SuggestionCardDescription(model.description)
+ }
+ if (model.onDismiss != null) {
+ Spacer(Modifier.padding(SettingsDimension.paddingSmall))
+ SuggestionCardDismissButton(model.onDismiss)
+ }
+ }
+ }
+}
+
+@Composable
+private fun SuggestionCardIcon(imageVector: ImageVector) {
+ Box(
+ modifier =
+ Modifier.padding(SettingsDimension.paddingSmall)
+ .size(SettingsDimension.itemIconContainerSizeSmall)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.secondary),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSecondary,
+ )
+ }
+}
+
+@Composable
+private fun SuggestionCardTitle(title: String) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(),
+ modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun SuggestionCardDescription(description: String) {
+ Text(
+ text = description,
+ style = MaterialTheme.typography.bodySmallEmphasized,
+ modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+}
+
+@Composable
+private fun SuggestionCardDismissButton(onDismiss: () -> Unit) {
+ IconButton(shape = CircleShape, onClick = onDismiss) {
+ Icon(
+ imageVector = Icons.Filled.Close,
+ contentDescription =
+ stringResource(androidx.compose.material3.R.string.m3c_snackbar_dismiss),
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SuggestionCardPreview() {
+ SettingsTheme {
+ SuggestionCard(
+ SuggestionCardModel(
+ title = "Suggestion card",
+ description = "Suggestion card description",
+ imageVector = Icons.Outlined.Stars,
+ onDismiss = {},
+ onClick = {},
+ )
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index acbdec0b30aa..66680fa547b1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -49,7 +49,9 @@ fun CategoryTitle(title: String) {
text = title,
modifier =
Modifier.padding(
- start = SettingsDimension.itemPaddingStart,
+ start =
+ if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
+ else SettingsDimension.itemPaddingStart,
top = 20.dp,
end =
if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
@@ -67,16 +69,16 @@ fun CategoryTitle(title: String) {
*/
@Composable
fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) {
+ var displayTitle by remember { mutableStateOf(false) }
Column(
modifier =
- if (isSpaExpressiveEnabled)
+ if (isSpaExpressiveEnabled && displayTitle)
Modifier.padding(
horizontal = SettingsDimension.paddingLarge,
vertical = SettingsDimension.paddingSmall,
)
else Modifier
) {
- var displayTitle by remember { mutableStateOf(false) }
if (title != null && displayTitle) CategoryTitle(title = title)
Column(
modifier =
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt
index a8479b01a861..a8479b01a861 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt
index 1080fdea9455..1080fdea9455 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt
new file mode 100644
index 000000000000..96bfb3d71642
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.card
+
+import android.content.Context
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Star
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.isNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SuggestionCardTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun suggestionCard_contentDisplayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+ }
+
+ @Test
+ fun suggestionCard_dismiss() {
+ setContent()
+ composeTestRule
+ .onNodeWithContentDescription(
+ context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
+ )
+ .performClick()
+
+ composeTestRule.onNodeWithText(TITLE).isNotDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION).isNotDisplayed()
+ }
+
+ private fun setContent() {
+ composeTestRule.setContent {
+ var isVisible by rememberSaveable { mutableStateOf(true) }
+ SuggestionCard(
+ SuggestionCardModel(
+ title = TITLE,
+ description = DESCRIPTION,
+ imageVector = Icons.Outlined.Star,
+ isVisible = isVisible,
+ onDismiss = { isVisible = false },
+ )
+ )
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val DESCRIPTION = "Description"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index f306918ec72f..d89d3977cac3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -39,6 +39,8 @@ import androidx.compose.ui.unit.Dp
import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.widget.preference.IntroAppPreference
import com.android.settingslib.spa.widget.ui.CopyableBody
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -48,23 +50,53 @@ import com.android.settingslib.spaprivileged.model.app.rememberAppRepository
class AppInfoProvider(private val packageInfo: PackageInfo) {
@Composable
fun AppInfo(displayVersion: Boolean = false, isClonedAppPage: Boolean = false) {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- horizontal = SettingsDimension.itemPaddingStart,
- vertical = SettingsDimension.itemPaddingVertical,
- )
- .semantics(mergeDescendants = true) {},
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
+ if (isSpaExpressiveEnabled) {
+ val appRepository = rememberAppRepository()
val app = checkNotNull(packageInfo.applicationInfo)
- Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
- AppIcon(app = app, size = SettingsDimension.appIconInfoSize)
+ val title = appRepository.produceLabel(app, isClonedAppPage).value
+
+ val descriptions = mutableListOf<String>()
+ if (app.isInstantApp) {
+ descriptions.add(
+ stringResource(
+ com.android.settingslib.widget.preference.app.R.string.install_type_instant
+ )
+ )
+ }
+ if (displayVersion) {
+ val versionName = packageInfo.versionNameBidiWrapped
+ if (versionName != null) descriptions.add(versionName)
+ }
+
+ IntroAppPreference(
+ title = title,
+ descriptions = descriptions,
+ appIcon = {
+ Image(
+ painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
+ contentDescription = appRepository.produceIconContentDescription(app).value,
+ )
+ },
+ )
+ } else {
+ Column(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = SettingsDimension.itemPaddingStart,
+ vertical = SettingsDimension.itemPaddingVertical,
+ )
+ .semantics(mergeDescendants = true) {},
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ val app = checkNotNull(packageInfo.applicationInfo)
+ Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
+ AppIcon(app = app, size = SettingsDimension.appIconInfoSize)
+ }
+ AppLabel(app, isClonedAppPage)
+ InstallType(app)
+ if (displayVersion) AppVersion()
}
- AppLabel(app, isClonedAppPage)
- InstallType(app)
- if (displayVersion) AppVersion()
}
}
@@ -89,19 +121,24 @@ class AppInfoProvider(private val packageInfo: PackageInfo) {
@Composable
fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
val context = LocalContext.current
- val footer = remember(packageInfo, showPackageName) {
- val list = mutableListOf<String>()
- packageInfo.versionNameBidiWrapped?.let {
- list += context.getString(R.string.version_text, it)
+ val footer =
+ remember(packageInfo, showPackageName) {
+ val list = mutableListOf<String>()
+ packageInfo.versionNameBidiWrapped?.let {
+ list += context.getString(R.string.version_text, it)
+ }
+ if (showPackageName) {
+ list += packageInfo.packageName
+ }
+ list.joinToString(separator = System.lineSeparator())
}
- if (showPackageName) {
- list += packageInfo.packageName
- }
- list.joinToString(separator = System.lineSeparator())
- }
if (footer.isBlank()) return
HorizontalDivider()
- Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+ Column(
+ modifier =
+ if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding)
+ else Modifier.padding(SettingsDimension.itemPadding)
+ ) {
CopyableBody(footer)
}
}
@@ -109,9 +146,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) {
@Composable
private fun rememberIsDevelopmentSettingsEnabled(): Boolean {
val context = LocalContext.current
- return remember {
- DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
- }
+ return remember { DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context) }
}
private companion object {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index abc58ee99904..fd2a1cb14edd 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -819,6 +819,11 @@
<!-- Summary of checkbox setting that enables the terminal app. [CHAR LIMIT=64] -->
<string name="enable_terminal_summary">Enable terminal app that offers local shell access</string>
+ <!-- Title of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=32] -->
+ <string name="enable_linux_terminal_title">Linux development environment</string>
+ <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=64] -->
+ <string name="enable_linux_terminal_summary">Run Linux terminal on Android</string>
+
<!-- HDCP checking title, used for debug purposes only. [CHAR LIMIT=25] -->
<string name="hdcp_checking_title">HDCP checking</string>
<!-- HDCP checking dialog title, used for debug purposes only. [CHAR LIMIT=25] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index d4d2b48fcc04..d91c6bd8e639 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -30,11 +30,12 @@ import android.view.DisplayInfo;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.settingslib.R;
import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
import java.util.function.Predicate;
/**
@@ -82,38 +83,55 @@ public class DisplayDensityUtils {
private final DisplayManager mDisplayManager;
/**
- * The text description of the density values of the default display.
- */
- private String[] mDefaultDisplayDensityEntries;
-
- /**
- * The density values of the default display.
+ * The text description of the density values.
*/
- private int[] mDefaultDisplayDensityValues;
+ @Nullable
+ private final String[] mEntries;
/**
- * The density values, indexed by display unique ID.
+ * The density values.
*/
- private final Map<String, int[]> mValuesPerDisplay = new HashMap();
+ @Nullable
+ private final int[] mValues;
- private int mDefaultDensityForDefaultDisplay;
- private int mCurrentIndex = -1;
+ private final int mDefaultDensity;
+ private final int mCurrentIndex;
- public DisplayDensityUtils(Context context) {
+ public DisplayDensityUtils(@NonNull Context context) {
this(context, INTERNAL_ONLY);
}
/**
- * Creates an instance that stores the density values for the displays that satisfy
- * the predicate.
+ * Creates an instance that stores the density values for the smallest display that satisfies
+ * the predicate. It is enough to store the values for one display because the same density
+ * should be set to all the displays that satisfy the predicate.
* @param context The context
* @param predicate Determines what displays the density should be set for. The default display
* must satisfy this predicate.
*/
- public DisplayDensityUtils(Context context, Predicate predicate) {
+ public DisplayDensityUtils(@NonNull Context context,
+ @NonNull Predicate<DisplayInfo> predicate) {
mPredicate = predicate;
mDisplayManager = context.getSystemService(DisplayManager.class);
+ Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ DisplayInfo defaultDisplayInfo = new DisplayInfo();
+ if (!defaultDisplay.getDisplayInfo(defaultDisplayInfo)) {
+ Log.w(LOG_TAG, "Cannot fetch display info for the default display");
+ mEntries = null;
+ mValues = null;
+ mDefaultDensity = 0;
+ mCurrentIndex = -1;
+ return;
+ }
+ if (!mPredicate.test(defaultDisplayInfo)) {
+ throw new IllegalArgumentException(
+ "Predicate must not filter out the default display.");
+ }
+
+ int idOfSmallestDisplay = Display.DEFAULT_DISPLAY;
+ int minDimensionPx = Math.min(defaultDisplayInfo.logicalWidth,
+ defaultDisplayInfo.logicalHeight);
for (Display display : mDisplayManager.getDisplays(
DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
DisplayInfo info = new DisplayInfo();
@@ -122,121 +140,123 @@ public class DisplayDensityUtils {
continue;
}
if (!mPredicate.test(info)) {
- if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
- throw new IllegalArgumentException("Predicate must not filter out the default "
- + "display.");
- }
continue;
}
-
- final int defaultDensity = DisplayDensityUtils.getDefaultDensityForDisplay(
- display.getDisplayId());
- if (defaultDensity <= 0) {
- Log.w(LOG_TAG, "Cannot fetch default density for display "
- + display.getDisplayId());
- continue;
+ int minDimension = Math.min(info.logicalWidth, info.logicalHeight);
+ if (minDimension < minDimensionPx) {
+ minDimensionPx = minDimension;
+ idOfSmallestDisplay = display.getDisplayId();
}
+ }
- final Resources res = context.getResources();
-
- final int currentDensity = info.logicalDensityDpi;
- int currentDensityIndex = -1;
-
- // Compute number of "larger" and "smaller" scales for this display.
- final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
- final int maxDensity =
- DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
- final float maxScaleDimen = context.getResources().getFraction(
- R.fraction.display_density_max_scale, 1, 1);
- final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
- final float minScale = context.getResources().getFraction(
- R.fraction.display_density_min_scale, 1, 1);
- final float minScaleInterval = context.getResources().getFraction(
- R.fraction.display_density_min_scale_interval, 1, 1);
- final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
- 0, SUMMARIES_LARGER.length);
- final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
- 0, SUMMARIES_SMALLER.length);
-
- String[] entries = new String[1 + numSmaller + numLarger];
- int[] values = new int[entries.length];
- int curIndex = 0;
-
- if (numSmaller > 0) {
- final float interval = (1 - minScale) / numSmaller;
- for (int i = numSmaller - 1; i >= 0; i--) {
- // Round down to a multiple of 2 by truncating the low bit.
- final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
- if (currentDensity == density) {
- currentDensityIndex = curIndex;
- }
- entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
- values[curIndex] = density;
- curIndex++;
+ final int defaultDensity =
+ DisplayDensityUtils.getDefaultDensityForDisplay(idOfSmallestDisplay);
+ if (defaultDensity <= 0) {
+ Log.w(LOG_TAG, "Cannot fetch default density for display " + idOfSmallestDisplay);
+ mEntries = null;
+ mValues = null;
+ mDefaultDensity = 0;
+ mCurrentIndex = -1;
+ return;
+ }
+
+ final Resources res = context.getResources();
+
+ final int currentDensity = defaultDisplayInfo.logicalDensityDpi;
+ int currentDensityIndex = -1;
+
+ // Compute number of "larger" and "smaller" scales for this display.
+ final int maxDensity =
+ DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
+ final float maxScaleDimen = context.getResources().getFraction(
+ R.fraction.display_density_max_scale, 1, 1);
+ final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
+ final float minScale = context.getResources().getFraction(
+ R.fraction.display_density_min_scale, 1, 1);
+ final float minScaleInterval = context.getResources().getFraction(
+ R.fraction.display_density_min_scale_interval, 1, 1);
+ final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
+ 0, SUMMARIES_LARGER.length);
+ final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
+ 0, SUMMARIES_SMALLER.length);
+
+ String[] entries = new String[1 + numSmaller + numLarger];
+ int[] values = new int[entries.length];
+ int curIndex = 0;
+
+ if (numSmaller > 0) {
+ final float interval = (1 - minScale) / numSmaller;
+ for (int i = numSmaller - 1; i >= 0; i--) {
+ // Round down to a multiple of 2 by truncating the low bit.
+ final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
+ if (currentDensity == density) {
+ currentDensityIndex = curIndex;
}
+ entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
+ values[curIndex] = density;
+ curIndex++;
}
+ }
- if (currentDensity == defaultDensity) {
- currentDensityIndex = curIndex;
- }
- values[curIndex] = defaultDensity;
- entries[curIndex] = res.getString(SUMMARY_DEFAULT);
- curIndex++;
-
- if (numLarger > 0) {
- final float interval = (maxScale - 1) / numLarger;
- for (int i = 0; i < numLarger; i++) {
- // Round down to a multiple of 2 by truncating the low bit.
- final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
- if (currentDensity == density) {
- currentDensityIndex = curIndex;
- }
- values[curIndex] = density;
- entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
- curIndex++;
+ if (currentDensity == defaultDensity) {
+ currentDensityIndex = curIndex;
+ }
+ values[curIndex] = defaultDensity;
+ entries[curIndex] = res.getString(SUMMARY_DEFAULT);
+ curIndex++;
+
+ if (numLarger > 0) {
+ final float interval = (maxScale - 1) / numLarger;
+ for (int i = 0; i < numLarger; i++) {
+ // Round down to a multiple of 2 by truncating the low bit.
+ final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
+ if (currentDensity == density) {
+ currentDensityIndex = curIndex;
}
+ values[curIndex] = density;
+ entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
+ curIndex++;
}
+ }
- final int displayIndex;
- if (currentDensityIndex >= 0) {
- displayIndex = currentDensityIndex;
- } else {
- // We don't understand the current density. Must have been set by
- // someone else. Make room for another entry...
- int newLength = values.length + 1;
- values = Arrays.copyOf(values, newLength);
- values[curIndex] = currentDensity;
+ final int displayIndex;
+ if (currentDensityIndex >= 0) {
+ displayIndex = currentDensityIndex;
+ } else {
+ // We don't understand the current density. Must have been set by
+ // someone else. Make room for another entry...
+ int newLength = values.length + 1;
+ values = Arrays.copyOf(values, newLength);
+ values[curIndex] = currentDensity;
- entries = Arrays.copyOf(entries, newLength);
- entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
+ entries = Arrays.copyOf(entries, newLength);
+ entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
- displayIndex = curIndex;
- }
-
- if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mDefaultDensityForDefaultDisplay = defaultDensity;
- mCurrentIndex = displayIndex;
- mDefaultDisplayDensityEntries = entries;
- mDefaultDisplayDensityValues = values;
- }
- mValuesPerDisplay.put(info.uniqueId, values);
+ displayIndex = curIndex;
}
+
+ mDefaultDensity = defaultDensity;
+ mCurrentIndex = displayIndex;
+ mEntries = entries;
+ mValues = values;
}
- public String[] getDefaultDisplayDensityEntries() {
- return mDefaultDisplayDensityEntries;
+ @Nullable
+ public String[] getEntries() {
+ return mEntries;
}
- public int[] getDefaultDisplayDensityValues() {
- return mDefaultDisplayDensityValues;
+ @Nullable
+ public int[] getValues() {
+ return mValues;
}
- public int getCurrentIndexForDefaultDisplay() {
+ public int getCurrentIndex() {
return mCurrentIndex;
}
- public int getDefaultDensityForDefaultDisplay() {
- return mDefaultDensityForDefaultDisplay;
+ public int getDefaultDensity() {
+ return mDefaultDensity;
}
/**
@@ -311,15 +331,9 @@ public class DisplayDensityUtils {
if (!mPredicate.test(info)) {
continue;
}
- if (!mValuesPerDisplay.containsKey(info.uniqueId)) {
- Log.w(LOG_TAG, "Unable to save forced display density setting "
- + "for display " + info.uniqueId);
- continue;
- }
final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- wm.setForcedDisplayDensityForUser(displayId,
- mValuesPerDisplay.get(info.uniqueId)[index], userId);
+ wm.setForcedDisplayDensityForUser(displayId, mValues[index], userId);
}
} catch (RemoteException exc) {
Log.w(LOG_TAG, "Unable to save forced display density setting");
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 6335e712f904..83ee9751329f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -125,7 +125,9 @@ public class InputMediaDevice extends MediaDevice {
? mProductName
: mContext.getString(R.string.media_transfer_usb_device_mic_name);
case TYPE_BLUETOOTH_SCO ->
- mContext.getString(R.string.media_transfer_bt_device_mic_name);
+ mProductName != null
+ ? mProductName
+ : mContext.getString(R.string.media_transfer_bt_device_mic_name);
default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
};
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 6c1cb7015225..7775b912e51d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -44,6 +44,7 @@ public class InputMediaDeviceTest {
private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic";
private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
+ private static final String PRODUCT_NAME_BT_HEADSET = "My Bluetooth Headset";
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -142,6 +143,21 @@ public class InputMediaDeviceTest {
MAX_VOLUME,
CURRENT_VOLUME,
IS_VOLUME_FIXED,
+ PRODUCT_NAME_BT_HEADSET);
+ assertThat(btMediaDevice).isNotNull();
+ assertThat(btMediaDevice.getName()).isEqualTo(PRODUCT_NAME_BT_HEADSET);
+ }
+
+ @Test
+ public void getName_returnCorrectName_btHeadset_nullProductName() {
+ InputMediaDevice btMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(BT_HEADSET_ID),
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED,
null);
assertThat(btMediaDevice).isNotNull();
assertThat(btMediaDevice.getName())
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 408ed1e861c3..b385aaa5c7d9 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -924,6 +924,7 @@
<!-- Permission required for CTS test - CtsPackageManagerTestCases-->
<uses-permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT" />
+ <uses-permission android:name="android.permission.VERIFICATION_AGENT" />
<!-- Permission required for Cts test - CtsInputTestCases -->
<uses-permission
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ef3b677667c3..3c560fdcadb6 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -87,7 +87,6 @@ filegroup {
srcs: [
"tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
"tests/src/**/systemui/broadcast/ActionReceiverTest.kt",
- "tests/src/**/systemui/doze/DozeMachineTest.java",
"tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
"tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
"tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
@@ -175,7 +174,6 @@ filegroup {
"tests/src/**/systemui/controls/management/ControlsEditingActivityTest.kt",
"tests/src/**/systemui/controls/management/ControlsRequestDialogTest.kt",
"tests/src/**/systemui/controls/ui/DetailDialogTest.kt",
- "tests/src/**/systemui/doze/DozeMachineTest.kt",
"tests/src/**/systemui/fontscaling/FontScalingDialogDelegateTest.kt",
"tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
"tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 540115de8830..651244a9e52a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1497,3 +1497,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "secondary_user_widget_host"
+ namespace: "systemui"
+ description: "Host communal widgets in the current secondary user on HSUM."
+ bug: "373874416"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 9dc93484a638..4cf264253bf8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -486,8 +486,8 @@ class TransitionAnimator(
endState: State,
windowBackgroundLayer: GradientDrawable,
fadeWindowBackgroundLayer: Boolean = true,
- useSpring: Boolean = false,
drawHole: Boolean = false,
+ useSpring: Boolean = false,
): Animation {
val transitionContainer = controller.transitionContainer
val transitionContainerOverlay = transitionContainer.overlay
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 9444664885c8..71230f9cde12 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -17,8 +17,9 @@
package com.android.systemui.keyguard.ui.composable.modifier
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
@@ -41,15 +42,17 @@ fun Modifier.burnInAware(
params: BurnInParameters,
isClock: Boolean = false,
): Modifier {
- val translationYState = remember { mutableStateOf(0F) }
- viewModel.updateBurnInParams(params.copy(translationY = { translationYState.value }))
+ val cachedYTranslation = remember { mutableFloatStateOf(0f) }
+ LaunchedEffect(Unit) {
+ viewModel.updateBurnInParams(params.copy(translationY = { cachedYTranslation.floatValue }))
+ }
val burnIn = viewModel.movement
val translationX by
burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
val translationY by
burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f)
- translationYState.value = translationY
+ cachedYTranslation.floatValue = translationY
val scaleViewModel by
burnIn
.map { BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index fe97405fab15..e9b7335197b0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -16,34 +16,58 @@
package com.android.systemui.statusbar.phone
+import android.app.Dialog
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.GravityInt
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.AnchoredDraggableDefaults
+import androidx.compose.foundation.gestures.AnchoredDraggableState
+import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
import com.android.compose.theme.PlatformTheme
+import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize
import com.android.systemui.res.R
+import kotlin.math.roundToInt
/**
* Create a [SystemUIDialog] with the given [content].
@@ -97,6 +121,9 @@ fun SystemUIDialogFactory.createBottomSheet(
theme: Int = R.style.Theme_SystemUI_BottomSheet,
dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
content: @Composable (SystemUIDialog) -> Unit,
+ isDraggable: Boolean = true,
+ // TODO(b/337205027): remove maxWidth parameter when aligned to M3 spec
+ maxWidth: Dp = Dp.Unspecified,
): ComponentSystemUIDialog {
return create(
context = context,
@@ -104,9 +131,49 @@ fun SystemUIDialogFactory.createBottomSheet(
dismissOnDeviceLock = dismissOnDeviceLock,
delegate = EdgeToEdgeDialogDelegate(),
content = { dialog ->
+ val dragState =
+ if (isDraggable)
+ remember { AnchoredDraggableState(initialValue = DragAnchors.Start) }
+ else null
+ val interactionSource =
+ if (isDraggable) remember { MutableInteractionSource() } else null
+ if (dragState != null) {
+ val isDragged by interactionSource!!.collectIsDraggedAsState()
+ LaunchedEffect(dragState.currentValue, isDragged) {
+ if (!isDragged && dragState.currentValue == DragAnchors.End) dialog.dismiss()
+ }
+ }
Box(
- modifier = Modifier.bottomSheetClickable { dialog.dismiss() },
- contentAlignment = Alignment.BottomCenter
+ modifier =
+ Modifier.bottomSheetClickable { dialog.dismiss() }
+ .then(
+ if (isDraggable)
+ Modifier.anchoredDraggable(
+ state = dragState!!,
+ interactionSource = interactionSource,
+ orientation = Orientation.Vertical,
+ flingBehavior =
+ AnchoredDraggableDefaults.flingBehavior(
+ state = dragState
+ ),
+ )
+ .offset {
+ IntOffset(x = 0, y = dragState.requireOffset().roundToInt())
+ }
+ .onSizeChanged { layoutSize ->
+ val dragEndPoint = layoutSize.height - dialog.height
+ dragState.updateAnchors(
+ DraggableAnchors {
+ DragAnchors.entries.forEach { anchor ->
+ anchor at dragEndPoint * anchor.fraction
+ }
+ }
+ )
+ }
+ .padding(top = draggableTopPadding())
+ else Modifier // No-Op
+ ),
+ contentAlignment = Alignment.BottomCenter,
) {
val radius = dimensionResource(R.dimen.bottom_sheet_corner_radius)
Surface(
@@ -114,8 +181,11 @@ fun SystemUIDialogFactory.createBottomSheet(
Modifier.bottomSheetPaddings()
// consume input so it doesn't get to the parent Composable
.bottomSheetClickable {}
- // TODO(b/337205027) change width
- .widthIn(max = 800.dp),
+ .widthIn(
+ max =
+ if (maxWidth.isSpecified) maxWidth
+ else DraggableBottomSheet.MaxWidth
+ ),
shape = RoundedCornerShape(topStart = radius, topEnd = radius),
color = MaterialTheme.colorScheme.surfaceContainer,
) {
@@ -127,7 +197,17 @@ fun SystemUIDialogFactory.createBottomSheet(
}
)
) {
- content(dialog)
+ if (isDraggable) {
+ Column(
+ Modifier.wrapContentWidth(Alignment.CenterHorizontally),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ DragHandle(dialog)
+ content(dialog)
+ }
+ } else {
+ content(dialog)
+ }
}
}
}
@@ -135,6 +215,11 @@ fun SystemUIDialogFactory.createBottomSheet(
)
}
+private enum class DragAnchors(val fraction: Float) {
+ Start(0f),
+ End(1f),
+}
+
private fun SystemUIDialogFactory.create(
context: Context,
theme: Int,
@@ -177,7 +262,7 @@ private fun Modifier.bottomSheetPaddings(): Modifier {
padding(
start = insets.getLeft(this, LocalLayoutDirection.current).toDp() + horizontalPadding,
top = insets.getTop(this).toDp(),
- end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding
+ end = insets.getRight(this, LocalLayoutDirection.current).toDp() + horizontalPadding,
)
}
}
@@ -191,3 +276,32 @@ private fun Modifier.bottomSheetPaddings(): Modifier {
@Composable
private fun Modifier.bottomSheetClickable(onClick: () -> Unit) =
pointerInput(onClick) { detectTapGestures { onClick() } }
+
+@Composable
+private fun DragHandle(dialog: Dialog) {
+ // TODO(b/373340318): Rename drag handle string resource.
+ val dragHandleContentDescription =
+ stringResource(id = R.string.shortcut_helper_content_description_drag_handle)
+ Surface(
+ modifier =
+ Modifier.padding(top = 16.dp, bottom = 6.dp)
+ .semantics { contentDescription = dragHandleContentDescription }
+ .clickable { dialog.dismiss() },
+ color = MaterialTheme.colorScheme.outlineVariant,
+ shape = MaterialTheme.shapes.extraLarge,
+ ) {
+ Box(Modifier.size(width = 32.dp, height = 4.dp))
+ }
+}
+
+@Composable
+private fun draggableTopPadding(): Dp {
+ return if (hasCompactWindowSize()) DraggableBottomSheet.DefaultTopPadding
+ else DraggableBottomSheet.LargeScreenTopPadding
+}
+
+private object DraggableBottomSheet {
+ val DefaultTopPadding = 64.dp
+ val LargeScreenTopPadding = 72.dp
+ val MaxWidth = 640.dp
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 1cc0fb2aad9b..cf74785b6d4a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -31,7 +31,6 @@ import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.theme.PlatformTheme
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -43,30 +42,20 @@ private const val VolumePanelTestTag = "VolumePanel"
private val padding = 24.dp
@Composable
-fun VolumePanelRoot(
- viewModel: VolumePanelViewModel,
- modifier: Modifier = Modifier,
-) {
+fun VolumePanelRoot(viewModel: VolumePanelViewModel, modifier: Modifier = Modifier) {
val accessibilityTitle = stringResource(R.string.accessibility_volume_settings)
val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle()
val components by viewModel.componentsLayout.collectAsStateWithLifecycle()
with(VolumePanelComposeScope(state)) {
components?.let { componentsState ->
- PlatformTheme {
- Components(
- componentsState,
- modifier
- .sysuiResTag(VolumePanelTestTag)
- .semantics { paneTitle = accessibilityTitle }
- .padding(
- start = padding,
- top = padding,
- end = padding,
- bottom = 20.dp,
- )
- )
- }
+ Components(
+ componentsState,
+ modifier
+ .sysuiResTag(VolumePanelTestTag)
+ .semantics { paneTitle = accessibilityTitle }
+ .padding(start = padding, top = padding, end = padding, bottom = 20.dp),
+ )
}
}
}
@@ -74,7 +63,7 @@ fun VolumePanelRoot(
@Composable
private fun VolumePanelComposeScope.Components(
layout: ComponentsLayout,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
val arrangement: Arrangement.Vertical =
if (isLargeScreen) {
@@ -82,14 +71,11 @@ private fun VolumePanelComposeScope.Components(
} else {
if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp)
}
- Column(
- modifier = modifier,
- verticalArrangement = arrangement,
- ) {
+ Column(modifier = modifier, verticalArrangement = arrangement) {
if (isPortrait || isLargeScreen) {
VerticalVolumePanelContent(
modifier = Modifier.weight(weight = 1f, fill = false),
- layout = layout
+ layout = layout,
)
} else {
HorizontalVolumePanelContent(
@@ -97,23 +83,17 @@ private fun VolumePanelComposeScope.Components(
layout = layout,
)
}
- BottomBar(
- modifier = Modifier,
- layout = layout,
- )
+ BottomBar(modifier = Modifier, layout = layout)
}
}
@Composable
private fun VolumePanelComposeScope.BottomBar(
layout: ComponentsLayout,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
if (layout.bottomBarComponent.isVisible) {
- Box(
- modifier = modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center,
- ) {
+ Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
Content(Modifier)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
index ca68c256fd73..772872719ebe 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt
@@ -22,19 +22,52 @@ interface ElementMatcher {
fun matches(key: ElementKey, content: ContentKey): Boolean
}
+/** Returns an [ElementMatcher] that matches any element in [content]. */
+fun inContent(content: ContentKey): ElementMatcher {
+ val matcherContent = content
+ return object : ElementMatcher {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return content == matcherContent
+ }
+ }
+}
+
+/** Returns an [ElementMatcher] that matches all elements not matching [this] matcher. */
+operator fun ElementMatcher.not(): ElementMatcher {
+ val delegate = this
+ return object : ElementMatcher {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return !delegate.matches(key, content)
+ }
+ }
+}
+
+/**
+ * Returns an [ElementMatcher] that matches all elements matching both [this] matcher and [other].
+ */
+infix fun ElementMatcher.and(other: ElementMatcher): ElementMatcher {
+ val delegate = this
+ return object : ElementMatcher {
+ override fun matches(key: ElementKey, content: ContentKey): Boolean {
+ return delegate.matches(key, content) && other.matches(key, content)
+ }
+ }
+}
+
/**
- * Returns an [ElementMatcher] that matches elements in [content] also matching [this]
- * [ElementMatcher].
+ * Returns an [ElementMatcher] that matches all elements either [this] matcher, or [other], or both.
*/
-fun ElementMatcher.inContent(content: ContentKey): ElementMatcher {
+infix fun ElementMatcher.or(other: ElementMatcher): ElementMatcher {
val delegate = this
- val matcherScene = content
return object : ElementMatcher {
override fun matches(key: ElementKey, content: ContentKey): Boolean {
- return content == matcherScene && delegate.matches(key, content)
+ return delegate.matches(key, content) || other.matches(key, content)
}
}
}
-@Deprecated("Use inContent() instead", replaceWith = ReplaceWith("inContent(scene)"))
-fun ElementMatcher.inScene(scene: SceneKey) = inContent(scene)
+@Deprecated(
+ "Use `this and inContent()` instead",
+ replaceWith = ReplaceWith("this and inContent(scene)"),
+)
+fun ElementMatcher.inScene(scene: SceneKey) = this and inContent(scene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 061583a64702..a3f2a434cff7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -81,16 +81,16 @@ private class SwipeToSceneRootNode(
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
) : DelegatingNode() {
- private var delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ private var delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) {
- if (draggableHandler == delegate.draggableHandler) {
+ if (draggableHandler == delegateNode.draggableHandler) {
// Simple update, just update the swipe detector directly and keep the node.
- delegate.swipeDetector = swipeDetector
+ delegateNode.swipeDetector = swipeDetector
} else {
// The draggableHandler changed, force recreate the underlying SwipeToSceneNode.
- undelegate(delegate)
- delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
+ undelegate(delegateNode)
+ delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector))
}
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt
new file mode 100644
index 000000000000..af0962361fb2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements.Bar
+import com.android.compose.animation.scene.TestElements.Foo
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ElementMatcherTest {
+ @Test
+ fun and() {
+ val matcher = Foo and inContent(SceneA)
+ assertThat(matcher.matches(Foo, SceneA)).isTrue()
+ assertThat(matcher.matches(Foo, SceneB)).isFalse()
+ assertThat(matcher.matches(Bar, SceneA)).isFalse()
+ assertThat(matcher.matches(Bar, SceneB)).isFalse()
+ }
+
+ @Test
+ fun or() {
+ val matcher = Foo or inContent(SceneA)
+ assertThat(matcher.matches(Foo, SceneA)).isTrue()
+ assertThat(matcher.matches(Foo, SceneB)).isTrue()
+ assertThat(matcher.matches(Bar, SceneA)).isTrue()
+ assertThat(matcher.matches(Bar, SceneB)).isFalse()
+ }
+
+ @Test
+ fun not() {
+ val matcher = !Foo
+ assertThat(matcher.matches(Foo, SceneA)).isFalse()
+ assertThat(matcher.matches(Foo, SceneB)).isFalse()
+ assertThat(matcher.matches(Bar, SceneA)).isTrue()
+ assertThat(matcher.matches(Bar, SceneB)).isTrue()
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 39d46990dc4b..ee807e6a7ede 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2221,8 +2221,8 @@ class ElementTest {
// In A => B, Foo is not shared and first fades out from A then fades in
// B.
sharedElement(TestElements.Foo, enabled = false)
- fractionRange(end = 0.5f) { fade(TestElements.Foo.inContent(SceneA)) }
- fractionRange(start = 0.5f) { fade(TestElements.Foo.inContent(SceneB)) }
+ fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) }
+ fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) }
}
from(SceneB, to = SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index cae6617cb11b..7ea414d6b8cd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -205,7 +206,8 @@ class OverlayTest {
val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB))
val elementChildTag = "elementChildTag"
- fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content)
+ fun elementChild(content: ContentKey) =
+ hasTestTag(elementChildTag) and SemanticsMatcher.inContent(content)
@Composable
fun ContentScope.MovableBar() {
@@ -773,7 +775,7 @@ class OverlayTest {
// Overscroll on Overlay A.
scope.launch { state.startTransition(transition(SceneA, OverlayA, progress = { 1.5f })) }
rule
- .onNode(hasTestTag(movableElementChildTag) and inContent(SceneA))
+ .onNode(hasTestTag(movableElementChildTag) and SemanticsMatcher.inContent(SceneA))
.assertPositionInRootIsEqualTo(0.dp, 0.dp)
.assertSizeIsEqualTo(100.dp)
.assertIsDisplayed()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index 4877cd610875..2e3a934c2701 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -31,7 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
-import com.android.compose.animation.scene.inContent
+import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
import com.android.compose.test.assertSizeIsEqualTo
import org.junit.Rule
@@ -125,10 +125,10 @@ class SharedElementTest {
sharedElement(TestElements.Foo, enabled = false)
// In SceneA, Foo leaves to the left edge.
- translate(TestElements.Foo.inContent(TestScenes.SceneA), Edge.Left)
+ translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left)
// In SceneB, Foo comes from the bottom edge.
- translate(TestElements.Foo.inContent(TestScenes.SceneB), Edge.Bottom)
+ translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom)
},
) {
before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
index 22450d32ea62..b3201d0daffe 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -25,11 +25,11 @@ fun isElement(element: ElementKey, content: ContentKey? = null): SemanticsMatche
return if (content == null) {
hasTestTag(element.testTag)
} else {
- hasTestTag(element.testTag) and inContent(content)
+ hasTestTag(element.testTag) and SemanticsMatcher.inContent(content)
}
}
/** A [SemanticsMatcher] that matches anything inside [content]. */
-fun inContent(content: ContentKey): SemanticsMatcher {
+fun SemanticsMatcher.Companion.inContent(content: ContentKey): SemanticsMatcher {
return hasAnyAncestor(hasTestTag(content.testTag))
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
index d001ef966c13..031fbabb239f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AssetLoader.kt
@@ -68,7 +68,10 @@ private constructor(
seedColor = null,
overrideChroma = null,
typefaceCache =
- TypefaceCache(messageBuffer) { Typeface.createFromAsset(pluginCtx.assets, it) },
+ TypefaceCache(messageBuffer) {
+ // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
+ return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL)
+ },
getThemeSeedColor = getThemeSeedColor ?: Companion::getThemeSeedColor,
messageBuffer = messageBuffer,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 3903dbaf64c6..900971bc3927 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -17,6 +17,8 @@ import android.content.Context
import android.content.res.Resources
import android.view.LayoutInflater
import com.android.systemui.customization.R
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
@@ -53,7 +55,9 @@ class DefaultClockProvider(
}
return if (clockReactiveVariants) {
- val assets = AssetLoader(ctx, ctx, "clocks/", messageBuffers!!.infraMessageBuffer)
+ val buffer =
+ messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
+ val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
FlexClockController(ctx, resources, assets, FLEX_DESIGN, messageBuffers)
} else {
DefaultClockController(
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 ef24d2ad3071..9067fb094634 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
@@ -71,7 +71,7 @@ class FlexClockFaceController(
val layer = face.layers[0]
layerController =
- if (isLargeClock)
+ if (isLargeClock) {
ComposedDigitalLayerController(
ctx,
resources,
@@ -79,7 +79,7 @@ class FlexClockFaceController(
layer as ComposedDigitalHandLayer,
messageBuffer,
)
- else {
+ } else {
val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
SimpleDigitalHandLayerController(
ctx,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 662815ee7cbe..fcb433b5db4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -46,7 +46,9 @@ import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
+import android.widget.Space;
import android.widget.Spinner;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -226,7 +228,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
mDialog.show();
LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
- assertThat(relatedToolsView.getChildCount()).isEqualTo(1);
+ assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1);
}
@Test
@@ -244,12 +246,13 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL);
when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable);
when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT);
+ when(mDrawable.mutate()).thenReturn(mDrawable);
setUpPairNewDeviceDialog();
mDialog.show();
LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
- assertThat(relatedToolsView.getChildCount()).isEqualTo(2);
+ assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2);
}
@Test
@@ -364,6 +367,16 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
return dialog.requireViewById(R.id.preset_spinner);
}
+ private int countChildWithoutSpace(ViewGroup viewGroup) {
+ int spaceCount = 0;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ if (viewGroup.getChildAt(i) instanceof Space) {
+ spaceCount++;
+ }
+ }
+ return viewGroup.getChildCount() - spaceCount;
+ }
+
@After
public void reset() {
if (mDialogDelegate != null) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
index 17ce1ddee87a..77369f7ec881 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java
@@ -85,7 +85,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase {
}
@Test
- public void parseStringArray_noString_emptyResult() {
+ public void parseStringArray_noToolName_emptyResult() {
assertThat(HearingDevicesToolItemParser.parseStringArray(mContext, new String[]{},
new String[]{})).isEqualTo(emptyList());
}
@@ -103,14 +103,14 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase {
}
@Test
- public void parseStringArray_fourToolName_maxThreeToolItem() {
+ public void parseStringArray_threeToolNames_maxTwoToolItems() {
String componentNameString = TEST_PKG + "/" + TEST_CLS;
- String[] fourToolName =
- new String[]{componentNameString, componentNameString, componentNameString,
- componentNameString};
+ String[] threeToolNames =
+ new String[]{componentNameString, componentNameString, componentNameString};
List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext,
- fourToolName, new String[]{});
+ threeToolNames, new String[]{});
+
assertThat(toolItemList.size()).isEqualTo(HearingDevicesToolItemParser.MAX_NUM);
}
@@ -120,6 +120,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase {
List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext,
wrongFormatToolName, new String[]{});
+
assertThat(toolItemList.size()).isEqualTo(0);
}
@@ -129,6 +130,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase {
List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext,
notExistToolName, new String[]{});
+
assertThat(toolItemList.size()).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt
index faaa4c415d28..1dd8ca9221a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt
@@ -14,23 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.window
+package com.android.systemui.display.data.repository
-import android.platform.test.annotations.EnableFlags
import android.view.Display
-import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
-import com.android.app.viewcapture.mockViewCaptureAwareWindowManager
import com.android.systemui.SysuiTestCase
-import com.android.systemui.display.data.repository.displayRepository
-import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.unconfinedTestDispatcher
-import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
@@ -43,28 +35,13 @@ import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
@SmallTest
-@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+class PerDisplayStoreImplTest : SysuiTestCase() {
private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
private val testScope = kosmos.testScope
private val fakeDisplayRepository = kosmos.displayRepository
- private val store =
- MultiDisplayStatusBarWindowControllerStore(
- backgroundApplicationScope = kosmos.applicationCoroutineScope,
- controllerFactory = kosmos.fakeStatusBarWindowControllerFactory,
- displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository,
- viewCaptureAwareWindowManagerFactory =
- object : ViewCaptureAwareWindowManager.Factory {
- override fun create(
- windowManager: WindowManager
- ): ViewCaptureAwareWindowManager {
- return kosmos.mockViewCaptureAwareWindowManager
- }
- },
- displayRepository = fakeDisplayRepository,
- )
+ private val store = kosmos.fakePerDisplayStore
@Before
fun start() {
@@ -80,34 +57,52 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
@Test
fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
testScope.runTest {
- val controller = store.defaultDisplay
+ val instance = store.defaultDisplay
- assertThat(store.defaultDisplay).isSameInstanceAs(controller)
+ assertThat(store.defaultDisplay).isSameInstanceAs(instance)
}
@Test
fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
testScope.runTest {
- val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
- assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(instance)
}
@Test
fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
testScope.runTest {
- val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
- assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(instance)
}
@Test(expected = IllegalArgumentException::class)
fun forDisplay_nonExistingDisplayId_throws() =
testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }
+ @Test
+ fun forDisplay_afterDisplayRemoved_onDisplayRemovalActionInvoked() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() =
+ testScope.runTest {
+ store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).isEmpty()
+ }
+
private fun createDisplay(displayId: Int): Display = mock {
on { getDisplayId() } doReturn displayId
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java
index fad52e090c69..ba43a236b8f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -38,9 +38,9 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.testing.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
new file mode 100644
index 000000000000..90727b240caf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.domain.ui.view
+
+import android.testing.TestableLooper
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
+import com.android.systemui.education.ui.view.ContextualEduDialog
+import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel
+import kotlin.test.assertEquals
+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.junit.MockitoJUnit
+import org.mockito.kotlin.firstValue
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class ContextualEduDialogTest : SysuiTestCase() {
+ @Rule
+ @JvmField
+ val activityRule: ActivityScenarioRule<EmptyTestActivity> =
+ ActivityScenarioRule(EmptyTestActivity::class.java)
+ @get:Rule val mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
+ private lateinit var underTest: ContextualEduDialog
+
+ @Before
+ fun setUp() {
+ whenever(accessibilityManager.isEnabled).thenReturn(true)
+ }
+
+ @Test
+ fun sendAccessibilityInfo() {
+ val message = "Testing message"
+ val viewModel = ContextualEduToastViewModel(message, icon = 0, userId = 0)
+ activityRule.scenario.onActivity {
+ underTest = ContextualEduDialog(context, viewModel, accessibilityManager)
+ underTest.show()
+ }
+
+ val eventCaptor = ArgumentCaptor.forClass(AccessibilityEvent::class.java)
+ verify(accessibilityManager).sendAccessibilityEvent(eventCaptor.capture())
+ assertEquals(message, eventCaptor.firstValue.text[0])
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 3fb3eead6469..b3417b9de36d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -150,6 +150,53 @@ class KeyguardInteractorTest : SysuiTestCase() {
assertThat(secureCameraActive()).isFalse()
}
+ /** Regression test for b/373700726. */
+ @Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testSecureCameraStillFalseAfterDeviceUnlocked() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ // Launch camera
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ assertThat(secureCameraActive()).isTrue()
+
+ // Go back to keyguard
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(false)
+ assertThat(secureCameraActive()).isFalse()
+
+ // WHEN device is unlocked (and therefore keyguard is no longer showing)
+ repository.setKeyguardShowing(false)
+
+ // THEN we still show secure camera as *not* active
+ assertThat(secureCameraActive()).isFalse()
+ }
+
+ /** Regression test for b/373700726. */
+ @Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testSecureCameraStillFalseAfterBouncerDismissed() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ // Launch camera
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ assertThat(secureCameraActive()).isTrue()
+
+ // Show bouncer
+ bouncerRepository.setPrimaryShow(true)
+ assertThat(secureCameraActive()).isFalse()
+
+ // WHEN device is unlocked (and therefore the bouncer is no longer showing)
+ bouncerRepository.setPrimaryShow(false)
+
+ // THEN we still show secure camera as *not* active
+ assertThat(secureCameraActive()).isFalse()
+ }
+
@Test
@DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
@@ -182,11 +229,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
val dismissAlpha by collectLastValue(underTest.dismissAlpha)
assertThat(dismissAlpha).isEqualTo(1f)
- keyguardTransitionRepository.sendTransitionSteps(
- from = AOD,
- to = LOCKSCREEN,
- testScope,
- )
+ keyguardTransitionRepository.sendTransitionSteps(from = AOD, to = LOCKSCREEN, testScope)
repository.setStatusBarState(StatusBarState.KEYGUARD)
// User begins to swipe up
@@ -208,11 +251,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
assertThat(dismissAlpha[0]).isEqualTo(1f)
assertThat(dismissAlpha.size).isEqualTo(1)
- keyguardTransitionRepository.sendTransitionSteps(
- from = AOD,
- to = LOCKSCREEN,
- testScope,
- )
+ keyguardTransitionRepository.sendTransitionSteps(from = AOD, to = LOCKSCREEN, testScope)
// User begins to swipe up
repository.setStatusBarState(StatusBarState.KEYGUARD)
@@ -328,11 +367,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(0f)
- keyguardTransitionRepository.sendTransitionSteps(
- from = AOD,
- to = LOCKSCREEN,
- testScope,
- )
+ keyguardTransitionRepository.sendTransitionSteps(from = AOD, to = LOCKSCREEN, testScope)
assertThat(keyguardTranslationY).isEqualTo(0f)
}
@@ -350,11 +385,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(1f)
- keyguardTransitionRepository.sendTransitionSteps(
- from = AOD,
- to = LOCKSCREEN,
- testScope,
- )
+ keyguardTransitionRepository.sendTransitionSteps(from = AOD, to = LOCKSCREEN, testScope)
assertThat(keyguardTranslationY).isEqualTo(0f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt
index fd1f52b9d8c8..ec0773f79328 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.panels.data.repository
-import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,7 +24,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -59,15 +58,22 @@ class QSColumnsRepositoryTest : SysuiTestCase() {
}
@Test
- @EnableFlags(DualShade.FLAG_NAME)
fun withDualShade_returnsCorrectValue() =
with(kosmos) {
testScope.runTest {
- val latest by collectLastValue(underTest.columns)
+ val latest by collectLastValue(underTest.dualShadeColumns)
+
assertThat(latest).isEqualTo(4)
+ }
+ }
+
+ @Test
+ fun withSplitShade_returnsCorrectValue() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(underTest.splitShadeColumns)
+ fakeShadeRepository.setShadeLayoutWide(true)
- setColumnsInConfig(8, id = R.integer.quick_settings_dual_shade_num_columns)
- // Asserts config changes are ignored
assertThat(latest).isEqualTo(4)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
new file mode 100644
index 000000000000..35f7504ead50
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.content.res.mainResources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
+import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QSColumnsInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ testCase.context.orCreateTestableResources.addOverride(
+ R.integer.quick_settings_infinite_grid_num_columns,
+ 1,
+ )
+ testCase.context.orCreateTestableResources.addOverride(
+ R.integer.quick_settings_dual_shade_num_columns,
+ 2,
+ )
+ testCase.context.orCreateTestableResources.addOverride(
+ R.integer.quick_settings_split_shade_num_columns,
+ 3,
+ )
+ qsColumnsRepository = QSColumnsRepository(mainResources, configurationRepository)
+ }
+ private lateinit var underTest: QSColumnsInteractor
+
+ @Before
+ fun setUp() {
+ underTest = with(kosmos) { qsColumnsInteractor }
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun withSingleShade_returnsCorrectValue() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(underTest.columns)
+
+ assertThat(latest).isEqualTo(1)
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun withDualShade_returnsCorrectValue() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(underTest.columns)
+
+ assertThat(latest).isEqualTo(2)
+ }
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun withSplitShade_returnsCorrectValue() =
+ with(kosmos) {
+ testScope.runTest {
+ val latest by collectLastValue(underTest.columns)
+
+ fakeShadeRepository.setShadeLayoutWide(true)
+
+ assertThat(latest).isEqualTo(3)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 7798f46fdb46..b23262a87e2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -34,9 +34,9 @@ import android.graphics.drawable.Drawable;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.Tile;
-import android.testing.UiThreadTest;
import android.widget.ImageView;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index c737bf7a8bec..e312d00f3dc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -20,12 +20,15 @@ import android.app.FragmentManager
import android.app.FragmentTransaction
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.view.ViewGroup
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.google.common.truth.Truth.assertThat
@@ -35,6 +38,8 @@ import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@@ -42,27 +47,31 @@ import org.mockito.kotlin.whenever
class StatusBarInitializerTest : SysuiTestCase() {
private val windowController = mock(StatusBarWindowController::class.java)
private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
+ private val transaction = mock(FragmentTransaction::class.java)
+ private val fragmentManager = mock(FragmentManager::class.java)
+ private val fragmentHostManager = mock(FragmentHostManager::class.java)
+ private val backgroundView = mock(ViewGroup::class.java)
@Before
fun setup() {
// TODO(b/364360986) this will go away once the fragment is deprecated. Hence, there is no
// need right now for moving this to kosmos
- val transaction = mock(FragmentTransaction::class.java)
- val fragmentManager = mock(FragmentManager::class.java)
- val fragmentHostManager = mock(FragmentHostManager::class.java)
whenever(fragmentHostManager.addTagListener(any(), any())).thenReturn(fragmentHostManager)
whenever(fragmentHostManager.fragmentManager).thenReturn(fragmentManager)
whenever(fragmentManager.beginTransaction()).thenReturn(transaction)
whenever(transaction.replace(any(), any(), any())).thenReturn(transaction)
whenever(windowControllerStore.defaultDisplay).thenReturn(windowController)
whenever(windowController.fragmentHostManager).thenReturn(fragmentHostManager)
+ whenever(windowController.backgroundView).thenReturn(backgroundView)
}
val underTest =
StatusBarInitializerImpl(
+ statusBarWindowController = windowController,
collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) },
+ statusBarRootFactory = mock(StatusBarRootFactory::class.java),
+ componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
creationListeners = setOf(),
- statusBarWindowController = windowController,
)
@Test
@@ -79,6 +88,15 @@ class StatusBarInitializerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+ fun simpleFragment_flagEnabled_doesNotCreateFragment() {
+ underTest.start()
+
+ verify(fragmentManager, never()).beginTransaction()
+ verify(transaction, never()).replace(any(), any(), any())
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
fun flagOff_doesNotInitializeViaCoreStartable() {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 48ae7a2aa260..feda0c65bd7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -36,7 +36,7 @@ import com.android.systemui.statusbar.phone.BoundsPair
import com.android.systemui.statusbar.phone.LetterboxAppearance
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -62,8 +62,8 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
private val commandQueue = mock<CommandQueue>()
private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>()
private val statusBarBoundsProvider = mock<StatusBarBoundsProvider>()
- private val statusBarFragmentComponent =
- mock<StatusBarFragmentComponent>().also {
+ private val homeStatusBarComponent =
+ mock<HomeStatusBarComponent>().also {
whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider)
}
private val ongoingCallRepository = kosmos.ongoingCallRepository
@@ -78,7 +78,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
)
.apply {
this.start()
- this.onStatusBarViewInitialized(statusBarFragmentComponent)
+ this.onStatusBarViewInitialized(homeStatusBarComponent)
}
private val commandQueueCallback: CommandQueue.Callbacks
@@ -235,9 +235,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.isInFullscreenMode)
- onSystemBarAttributesChanged(
- requestedVisibleTypes = WindowInsets.Type.statusBars(),
- )
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.statusBars())
assertThat(latest).isFalse()
}
@@ -247,9 +245,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.isInFullscreenMode)
- onSystemBarAttributesChanged(
- requestedVisibleTypes = WindowInsets.Type.navigationBars(),
- )
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
assertThat(latest).isTrue()
}
@@ -259,9 +255,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.isInFullscreenMode)
- onSystemBarAttributesChanged(
- requestedVisibleTypes = WindowInsets.Type.navigationBars(),
- )
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
assertThat(latest).isTrue()
onSystemBarAttributesChanged(
@@ -347,7 +341,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
val startingLetterboxAppearance =
LetterboxAppearance(
APPEARANCE_LIGHT_STATUS_BARS,
- listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1)))
+ listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1))),
)
whenever(
letterboxAppearanceCalculator.getLetterboxAppearance(
@@ -371,7 +365,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
val newLetterboxAppearance =
LetterboxAppearance(
APPEARANCE_LOW_PROFILE_BARS,
- listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40)))
+ listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40))),
)
whenever(
letterboxAppearanceCalculator.getLetterboxAppearance(
@@ -398,9 +392,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
val latest by collectLastValue(underTest.statusBarAppearance)
ongoingCallRepository.setOngoingCallState(inCallModel(startTimeMs = 34))
- onSystemBarAttributesChanged(
- requestedVisibleTypes = WindowInsets.Type.navigationBars(),
- )
+ onSystemBarAttributesChanged(requestedVisibleTypes = WindowInsets.Type.navigationBars())
assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
}
@@ -438,9 +430,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
fun statusBarMode_transientShown_semiTransparent() =
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- onSystemBarAttributesChanged(
- appearance = APPEARANCE_OPAQUE_STATUS_BARS,
- )
+ onSystemBarAttributesChanged(appearance = APPEARANCE_OPAQUE_STATUS_BARS)
underTest.showTransient()
@@ -453,7 +443,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
val latest by collectLastValue(underTest.statusBarAppearance)
onSystemBarAttributesChanged(
- appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS,
+ appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS
)
assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT)
@@ -464,9 +454,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- onSystemBarAttributesChanged(
- appearance = APPEARANCE_LOW_PROFILE_BARS,
- )
+ onSystemBarAttributesChanged(appearance = APPEARANCE_LOW_PROFILE_BARS)
assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT)
}
@@ -476,9 +464,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- onSystemBarAttributesChanged(
- appearance = APPEARANCE_OPAQUE_STATUS_BARS,
- )
+ onSystemBarAttributesChanged(appearance = APPEARANCE_OPAQUE_STATUS_BARS)
assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE)
}
@@ -488,9 +474,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- onSystemBarAttributesChanged(
- appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
- )
+ onSystemBarAttributesChanged(appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT)
}
@@ -500,9 +484,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- onSystemBarAttributesChanged(
- appearance = 0,
- )
+ onSystemBarAttributesChanged(appearance = 0)
assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT)
}
@@ -540,7 +522,7 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() {
LetterboxDetails(
/* letterboxInnerBounds= */ Rect(0, 0, 10, 10),
/* letterboxFullBounds= */ Rect(0, 0, 20, 20),
- /* appAppearance= */ 0
+ /* appAppearance= */ 0,
)
)
private val REGIONS_FROM_LETTERBOX_CALCULATOR =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index d04d6fc5f593..0947cd5aaabb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,12 +45,12 @@ import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.testing.UiThreadTest;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 22f1e4604bbd..6435e8203d3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -24,9 +24,9 @@ import static org.mockito.Mockito.mock;
import android.provider.Settings;
import android.testing.TestableResources;
-import android.testing.UiThreadTest;
import android.util.KeyValueListParser;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index c804fc6990ae..ba5fb7f8df00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -98,7 +98,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
@@ -135,7 +135,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
@@ -164,8 +164,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val chipWidth = 30
val dotWidth = 10
val isRtl = false
- val contentRect =
- Rect(/* left = */ 0, /* top = */ 10, /* right = */ 1000, /* bottom = */ 100)
+ val contentRect = Rect(/* left= */ 0, /* top= */ 10, /* right= */ 1000, /* bottom= */ 100)
val chipBounds =
getPrivacyChipBoundingRectForInsets(contentRect, dotWidth, chipWidth, isRtl)
@@ -207,7 +206,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -228,7 +227,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -251,7 +250,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -263,7 +262,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
minLeftPadding,
0,
screenBounds.height() - dcBounds.height() - dotWidth,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -278,7 +277,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -320,7 +319,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -331,7 +330,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
protectionBounds.bottom,
0,
screenBounds.height() - minRightPadding,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -346,7 +345,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -369,7 +368,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -381,7 +380,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
minLeftPadding,
0,
screenBounds.height() - protectionBounds.bottom - dotWidth,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -396,7 +395,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -415,7 +414,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = screenBounds.right - dcWidth,
top = 0,
right = screenBounds.right,
- bottom = dcHeight
+ bottom = dcHeight,
)
val dcBoundsLandscape = Rect(left = 0, top = 0, right = dcHeight, bottom = dcWidth)
val dcBoundsSeascape =
@@ -423,14 +422,14 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = screenBounds.right - dcHeight,
top = screenBounds.bottom - dcWidth,
right = screenBounds.right - dcHeight,
- bottom = screenBounds.bottom - dcWidth
+ bottom = screenBounds.bottom - dcWidth,
)
val dcBoundsUpsideDown =
Rect(
left = 0,
top = screenBounds.bottom - dcHeight,
right = dcWidth,
- bottom = screenBounds.bottom - dcHeight
+ bottom = screenBounds.bottom - dcHeight,
)
val minLeftPadding = 20
val minRightPadding = 20
@@ -448,7 +447,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = minLeftPadding,
top = 0,
right = dcBoundsPortrait.left - dotWidth,
- bottom = sbHeightPortrait
+ bottom = sbHeightPortrait,
)
var bounds =
@@ -463,7 +462,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -475,7 +474,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = dcBoundsLandscape.height(),
top = 0,
right = screenBounds.height() - minRightPadding,
- bottom = sbHeightLandscape
+ bottom = sbHeightLandscape,
)
bounds =
@@ -490,7 +489,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -502,7 +501,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = minLeftPadding,
top = 0,
right = screenBounds.width() - minRightPadding,
- bottom = sbHeightPortrait
+ bottom = sbHeightPortrait,
)
bounds =
@@ -517,7 +516,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -529,7 +528,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left = minLeftPadding,
top = 0,
right = screenBounds.height() - minRightPadding,
- bottom = sbHeightLandscape
+ bottom = sbHeightLandscape,
)
bounds =
@@ -544,7 +543,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -584,7 +583,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -595,7 +594,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
protectionBounds.bottom,
0,
screenBounds.height() - minRightPadding,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -610,7 +609,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -633,7 +632,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -645,7 +644,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
minLeftPadding,
0,
screenBounds.height() - protectionBounds.bottom - dotWidth,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -660,7 +659,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -682,7 +681,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl = false,
dotWidth = 10,
bottomAlignedMargin = BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight = 15
+ statusBarContentHeight = 15,
)
assertThat(bounds.top).isEqualTo(0)
@@ -704,7 +703,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl = false,
dotWidth = 10,
bottomAlignedMargin = 5,
- statusBarContentHeight = 15
+ statusBarContentHeight = 15,
)
// Content in the status bar is centered vertically. To achieve the bottom margin we want,
@@ -756,7 +755,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -777,7 +776,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -798,7 +797,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -809,7 +808,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
minLeftPadding,
0,
screenBounds.height() - dcBounds.height() - dotWidth,
- sbHeightLandscape
+ sbHeightLandscape,
)
bounds =
@@ -824,7 +823,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -860,7 +859,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -880,7 +879,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -900,7 +899,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -920,7 +919,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
}
@@ -958,7 +957,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
isRtl,
dotWidth,
BOTTOM_ALIGNED_MARGIN_NONE,
- statusBarContentHeight
+ statusBarContentHeight,
)
assertRects(expectedBounds, bounds, currentRotation, targetRotation)
@@ -968,12 +967,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
fun testDisplayChanged_returnsUpdatedInsets() {
// GIVEN: get insets on the first display and switch to the second display
val provider =
- StatusBarContentInsetsProvider(
+ StatusBarContentInsetsProviderImpl(
contextMock,
configurationController,
mock<DumpManager>(),
mock<CommandRegistry>(),
- mock<SysUICutoutProvider>()
+ mock<SysUICutoutProvider>(),
)
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
@@ -993,12 +992,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
// GIVEN: get insets on the first display, switch to the second display,
// get insets and switch back
val provider =
- StatusBarContentInsetsProvider(
+ StatusBarContentInsetsProviderImpl(
contextMock,
configurationController,
mock<DumpManager>(),
mock<CommandRegistry>(),
- mock<SysUICutoutProvider>()
+ mock<SysUICutoutProvider>(),
)
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
@@ -1024,12 +1023,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
val provider =
- StatusBarContentInsetsProvider(
+ StatusBarContentInsetsProviderImpl(
contextMock,
configurationController,
mock<DumpManager>(),
mock<CommandRegistry>(),
- mock<SysUICutoutProvider>()
+ mock<SysUICutoutProvider>(),
)
val listener =
object : StatusBarContentInsetsChangedListener {
@@ -1053,12 +1052,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
fun onDensityOrFontScaleChanged_listenerNotified() {
configuration.densityDpi = 12
val provider =
- StatusBarContentInsetsProvider(
+ StatusBarContentInsetsProviderImpl(
contextMock,
configurationController,
mock<DumpManager>(),
mock<CommandRegistry>(),
- mock<SysUICutoutProvider>()
+ mock<SysUICutoutProvider>(),
)
val listener =
object : StatusBarContentInsetsChangedListener {
@@ -1081,12 +1080,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
@Test
fun onThemeChanged_listenerNotified() {
val provider =
- StatusBarContentInsetsProvider(
+ StatusBarContentInsetsProviderImpl(
contextMock,
configurationController,
mock<DumpManager>(),
mock<CommandRegistry>(),
- mock<SysUICutoutProvider>()
+ mock<SysUICutoutProvider>(),
)
val listener =
object : StatusBarContentInsetsChangedListener {
@@ -1108,13 +1107,13 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
expected: Rect,
actual: Rect,
@Rotation currentRotation: Int,
- @Rotation targetRotation: Int
+ @Rotation targetRotation: Int,
) {
assertTrue(
"Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" +
" targetRotation=${RotationUtils.toString(targetRotation)}" +
" expected=$expected actual=$actual",
- expected.equals(actual)
+ expected.equals(actual),
)
}
@@ -1126,7 +1125,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
left: Rect = Rect(),
top: Rect = Rect(),
right: Rect = Rect(),
- bottom: Rect = Rect()
+ bottom: Rect = Rect(),
) {
whenever(dc.boundingRects)
.thenReturn(listOf(left, top, right, bottom).filter { !it.isEmpty })
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index 2ee928fa6d17..cdc7aa2dea2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
@@ -17,21 +17,21 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.view.View
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
/**
* A fake view binder that can be used from Java tests.
*
* Since Java tests can't run tests within test scopes, we need to bypass the flows from
- * [CollapsedStatusBarViewModel] and just trigger the listener directly.
+ * [HomeStatusBarViewModel] and just trigger the listener directly.
*/
-class FakeCollapsedStatusBarViewBinder : CollapsedStatusBarViewBinder {
+class FakeHomeStatusBarViewBinder : HomeStatusBarViewBinder {
var listener: StatusBarVisibilityChangeListener? = null
override fun bind(
view: View,
- viewModel: CollapsedStatusBarViewModel,
+ viewModel: HomeStatusBarViewModel,
listener: StatusBarVisibilityChangeListener,
) {
this.listener = listener
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index cc90c1167ef1..02c1540d3d8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
+class FakeHomeStatusBarViewModel : HomeStatusBarViewModel {
private val areNotificationLightsOut = MutableStateFlow(false)
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
@@ -39,7 +39,7 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
override val isClockVisible =
MutableStateFlow(
- CollapsedStatusBarViewModel.VisibilityModel(
+ HomeStatusBarViewModel.VisibilityModel(
visibility = View.GONE,
shouldAnimateChange = false,
)
@@ -47,7 +47,7 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
override val isNotificationIconContainerVisible =
MutableStateFlow(
- CollapsedStatusBarViewModel.VisibilityModel(
+ HomeStatusBarViewModel.VisibilityModel(
visibility = View.GONE,
shouldAnimateChange = false,
)
@@ -55,7 +55,7 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
override val isSystemInfoVisible =
MutableStateFlow(
- CollapsedStatusBarViewModel.VisibilityModel(
+ HomeStatusBarViewModel.VisibilityModel(
visibility = View.GONE,
shouldAnimateChange = false,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index bd857807851c..b3a73d82122f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
import android.app.StatusBarManager.DISABLE2_NONE
import android.app.StatusBarManager.DISABLE_CLOCK
import android.app.StatusBarManager.DISABLE_NONE
@@ -33,6 +34,7 @@ import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -75,7 +77,7 @@ import org.junit.runner.RunWith
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
-class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
+class HomeStatusBarViewModelImplTest : SysuiTestCase() {
private val kosmos =
Kosmos().also {
it.testCase = this
@@ -89,13 +91,13 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository
- private lateinit var underTest: CollapsedStatusBarViewModel
+ private lateinit var underTest: HomeStatusBarViewModel
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
// Initialize here because some flags are checked when this class is constructed
- underTest = kosmos.collapsedStatusBarViewModel
+ underTest = kosmos.homeStatusBarViewModel
}
@Test
@@ -746,6 +748,45 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
}
+ @Test
+ @DisableSceneContainer
+ fun secureCameraActive_sceneFlagOff_noStatusBarViewsShown() =
+ testScope.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+ // Secure camera is an occluding activity
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = this,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun secureCameraActive_sceneFlagOn_noStatusBarViewsShown() =
+ testScope.runTest {
+ val clockVisible by collectLastValue(underTest.isClockVisible)
+ val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible)
+ val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible)
+
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ // Secure camera is an occluding activity
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null)
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(clockVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE)
+ assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE)
+ }
+
private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
ActiveNotificationsStore.Builder()
.apply { notifications.forEach(::addIndividualNotif) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt
index 4a53a7a74bad..fd8958166a6c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ZenModesCleanupStartableTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy
import android.app.AutomaticZenRule
import android.app.NotificationManager
import android.net.Uri
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -42,6 +43,7 @@ import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
+@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ZenModesCleanupStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
index 40c3f221e2df..29e9ba752b36 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt
@@ -61,6 +61,26 @@ class BackGestureRecognizerTest : SysuiTestCase() {
}
@Test
+ fun triggersProgressRelativeToDistance() {
+ assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
+ assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
+ assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE, expectedProgress = 1f)
+ assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE, expectedProgress = 1f)
+ }
+
+ private fun assertProgressWhileMovingFingers(deltaX: Float, expectedProgress: Float) {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) },
+ expectedState = InProgress(progress = expectedProgress),
+ )
+ }
+
+ @Test
+ fun triggeredProgressIsNoBiggerThanOne() {
+ assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE * 2, expectedProgress = 1f)
+ }
+
+ @Test
fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
assertStateAfterEvents(
events = ThreeFingerGesture.swipeLeft(distancePx = SWIPE_DISTANCE / 2),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
index 8406d3b99bac..ff0cec5e06e9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
@@ -104,7 +104,8 @@ class EasterEggGestureTest : SysuiTestCase() {
}
private fun assertStateAfterTwoFingerGesture(gesturePath: List<Point>, wasTriggered: Boolean) {
- val events = TwoFingerGesture.createEvents { gesturePath.forEach { (x, y) -> move(x, y) } }
+ val events =
+ TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { (x, y) -> move(x, y) } }
assertStateAfterEvents(events = events, wasTriggered = wasTriggered)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
index 043b77577978..7d3ed92cecc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt
@@ -56,6 +56,27 @@ class HomeGestureRecognizerTest : SysuiTestCase() {
}
@Test
+ fun triggersProgressRelativeToDistance() {
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f)
+ }
+
+ private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) },
+ expectedState = InProgress(progress = expectedProgress),
+ )
+ }
+
+ @Test
+ fun triggeredProgressIsBetweenZeroAndOne() {
+ // going in the wrong direction
+ assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f)
+ // going further than required distance
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f)
+ }
+
+ @Test
fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
assertStateAfterEvents(
events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
index 7095a91a4e5d..c5c0d59ea48b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt
@@ -77,11 +77,32 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() {
fun triggersGestureProgressForThreeFingerGestureStarted() {
assertStateAfterEvents(
events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
- expectedState = InProgress(),
+ expectedState = InProgress(progress = 0f),
)
}
@Test
+ fun triggersProgressRelativeToDistance() {
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f)
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f)
+ }
+
+ private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) },
+ expectedState = InProgress(progress = expectedProgress),
+ )
+ }
+
+ @Test
+ fun triggeredProgressIsBetweenZeroAndOne() {
+ // going in the wrong direction
+ assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f)
+ // going further than required distance
+ assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f)
+ }
+
+ @Test
fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
assertStateAfterEvents(
events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
index 296d4dce8ce4..42fe1e5d6bec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt
@@ -25,11 +25,23 @@ import android.view.MotionEvent.ACTION_UP
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_X
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_Y
-/**
- * Interface for gesture builders which support creating list of [MotionEvent] for common swipe
- * gestures. For simple usage see swipe* methods or use [createEvents] for more specific scenarios.
- */
-interface MultiFingerGesture {
+/** Given gesture move events can build list of [MotionEvent]s included in that gesture */
+interface GestureEventsBuilder {
+ /**
+ * Creates full gesture including provided move events. This means returned events include DOWN,
+ * MOVE and UP. Note that move event's x and y is always relative to the starting one.
+ */
+ fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent>
+
+ /**
+ * Creates partial gesture including provided move events. This means returned events include
+ * DOWN and MOVE. Note that move event's x and y is always relative to the starting one.
+ */
+ fun eventsForGestureInProgress(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent>
+}
+
+/** Support creating list of [MotionEvent] for common swipe gestures. */
+interface MultiFingerGesture : GestureEventsBuilder {
companion object {
const val SWIPE_DISTANCE = 100f
@@ -37,27 +49,41 @@ interface MultiFingerGesture {
const val DEFAULT_Y = 500f
}
- fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = -distancePx) }
-
- fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = distancePx) }
+ fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
+ move(deltaY = -distancePx)
+ }
- fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = distancePx) }
+ fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
+ move(deltaY = distancePx)
+ }
- fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = -distancePx) }
+ fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
+ move(deltaX = distancePx)
+ }
- /**
- * Creates gesture with provided move events. Note that move event's x and y is always relative
- * to the starting one
- */
- fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent>
+ fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture {
+ move(deltaX = -distancePx)
+ }
}
object ThreeFingerGesture : MultiFingerGesture {
- override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
- return touchpadGesture(
+
+ private val moveEventsBuilder = MoveEventsBuilder(::threeFingerEvent)
+
+ override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
+ return buildGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
+ endEvents = { x, y -> endEvents(x, y) },
+ )
+ }
+
+ override fun eventsForGestureInProgress(
+ moveEvents: MoveEventsBuilder.() -> Unit
+ ): List<MotionEvent> {
+ return buildGesture(
startEvents = { x, y -> startEvents(x, y) },
- moveEvents = GestureBuilder(::threeFingerEvent).apply { moveEvents() }.events,
- endEvents = { x, y -> endEvents(x, y) }
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
)
}
@@ -65,7 +91,7 @@ object ThreeFingerGesture : MultiFingerGesture {
return listOf(
threeFingerEvent(ACTION_DOWN, x, y),
threeFingerEvent(ACTION_POINTER_DOWN, x, y),
- threeFingerEvent(ACTION_POINTER_DOWN, x, y)
+ threeFingerEvent(ACTION_POINTER_DOWN, x, y),
)
}
@@ -73,32 +99,43 @@ object ThreeFingerGesture : MultiFingerGesture {
return listOf(
threeFingerEvent(ACTION_POINTER_UP, x, y),
threeFingerEvent(ACTION_POINTER_UP, x, y),
- threeFingerEvent(ACTION_UP, x, y)
+ threeFingerEvent(ACTION_UP, x, y),
)
}
private fun threeFingerEvent(
action: Int,
x: Float = DEFAULT_X,
- y: Float = DEFAULT_Y
+ y: Float = DEFAULT_Y,
): MotionEvent {
return touchpadEvent(
action = action,
x = x,
y = y,
classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
- axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f)
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f),
)
}
}
object FourFingerGesture : MultiFingerGesture {
- override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
- return touchpadGesture(
+ private val moveEventsBuilder = MoveEventsBuilder(::fourFingerEvent)
+
+ override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
+ return buildGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
+ endEvents = { x, y -> endEvents(x, y) },
+ )
+ }
+
+ override fun eventsForGestureInProgress(
+ moveEvents: MoveEventsBuilder.() -> Unit
+ ): List<MotionEvent> {
+ return buildGesture(
startEvents = { x, y -> startEvents(x, y) },
- moveEvents = GestureBuilder(::fourFingerEvent).apply { moveEvents() }.events,
- endEvents = { x, y -> endEvents(x, y) }
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
)
}
@@ -107,7 +144,7 @@ object FourFingerGesture : MultiFingerGesture {
fourFingerEvent(ACTION_DOWN, x, y),
fourFingerEvent(ACTION_POINTER_DOWN, x, y),
fourFingerEvent(ACTION_POINTER_DOWN, x, y),
- fourFingerEvent(ACTION_POINTER_DOWN, x, y)
+ fourFingerEvent(ACTION_POINTER_DOWN, x, y),
)
}
@@ -116,61 +153,74 @@ object FourFingerGesture : MultiFingerGesture {
fourFingerEvent(ACTION_POINTER_UP, x, y),
fourFingerEvent(ACTION_POINTER_UP, x, y),
fourFingerEvent(ACTION_POINTER_UP, x, y),
- fourFingerEvent(ACTION_UP, x, y)
+ fourFingerEvent(ACTION_UP, x, y),
)
}
private fun fourFingerEvent(
action: Int,
x: Float = DEFAULT_X,
- y: Float = DEFAULT_Y
+ y: Float = DEFAULT_Y,
): MotionEvent {
return touchpadEvent(
action = action,
x = x,
y = y,
classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE,
- axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f)
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f),
)
}
}
object TwoFingerGesture : MultiFingerGesture {
- override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> {
- return touchpadGesture(
- startEvents = { x, y -> listOf(twoFingerEvent(ACTION_DOWN, x, y)) },
- moveEvents = GestureBuilder(::twoFingerEvent).apply { moveEvents() }.events,
- endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) }
+ private val moveEventsBuilder = MoveEventsBuilder(::twoFingerEvent)
+
+ override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> {
+ return buildGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
+ endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) },
+ )
+ }
+
+ override fun eventsForGestureInProgress(
+ moveEvents: MoveEventsBuilder.() -> Unit
+ ): List<MotionEvent> {
+ return buildGesture(
+ startEvents = { x, y -> startEvents(x, y) },
+ moveEvents = moveEventsBuilder.getEvents(moveEvents),
)
}
+ private fun startEvents(x: Float, y: Float) = listOf(twoFingerEvent(ACTION_DOWN, x, y))
+
private fun twoFingerEvent(
action: Int,
x: Float = DEFAULT_X,
- y: Float = DEFAULT_Y
+ y: Float = DEFAULT_Y,
): MotionEvent {
return touchpadEvent(
action = action,
x = x,
y = y,
classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE,
- axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f)
+ axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f),
)
}
}
-private fun touchpadGesture(
+private fun buildGesture(
startEvents: (Float, Float) -> List<MotionEvent>,
moveEvents: List<MotionEvent>,
- endEvents: (Float, Float) -> List<MotionEvent>
+ endEvents: (Float, Float) -> List<MotionEvent> = { _, _ -> emptyList() },
): List<MotionEvent> {
val lastX = moveEvents.last().x
val lastY = moveEvents.last().y
return startEvents(DEFAULT_X, DEFAULT_Y) + moveEvents + endEvents(lastX, lastY)
}
-class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) {
+class MoveEventsBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) {
val events = mutableListOf<MotionEvent>()
@@ -178,3 +228,11 @@ class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float)
events.add(eventBuilder(ACTION_MOVE, DEFAULT_X + deltaX, DEFAULT_Y + deltaY))
}
}
+
+private fun MoveEventsBuilder.getEvents(
+ moveEvents: MoveEventsBuilder.() -> Unit
+): List<MotionEvent> {
+ events.clear()
+ this.moveEvents()
+ return events
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
index 13ebb42531b8..64136775b4eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt
@@ -54,7 +54,28 @@ class TouchpadGestureBuilderTest : SysuiTestCase() {
ACTION_MOVE,
ACTION_POINTER_UP,
ACTION_POINTER_UP,
- ACTION_UP
+ ACTION_UP,
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun threeFingerGestureInProgressProducesCorrectEvents() {
+ val events =
+ ThreeFingerGesture.eventsForGestureInProgress {
+ move(deltaX = 10f)
+ move(deltaX = 20f)
+ }
+
+ val actions = events.map { it.actionMasked }
+ assertWithMessage("Events have expected action type")
+ .that(actions)
+ .containsExactly(
+ ACTION_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_POINTER_DOWN,
+ ACTION_MOVE,
+ ACTION_MOVE,
)
.inOrder()
}
@@ -80,7 +101,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() {
ACTION_POINTER_UP,
ACTION_POINTER_UP,
ACTION_POINTER_UP,
- ACTION_UP
+ ACTION_UP,
)
.inOrder()
}
@@ -109,7 +130,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() {
@Test
fun gestureBuilderProducesCorrectEventCoordinates() {
val events =
- ThreeFingerGesture.createEvents {
+ ThreeFingerGesture.eventsForFullGesture {
move(deltaX = 50f)
move(deltaX = 100f)
}
@@ -127,7 +148,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() {
// up events
DEFAULT_X + 100f to DEFAULT_Y,
DEFAULT_X + 100f to DEFAULT_Y,
- DEFAULT_X + 100f to DEFAULT_Y
+ DEFAULT_X + 100f to DEFAULT_Y,
)
.inOrder()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index a867eb38b44c..c302b40fc4d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -85,7 +85,7 @@ class TouchpadGestureHandlerTest : SysuiTestCase() {
}
private fun backGestureEvents(): List<MotionEvent> {
- return ThreeFingerGesture.createEvents {
+ return ThreeFingerGesture.eventsForFullGesture {
move(deltaX = SWIPE_DISTANCE / 4)
move(deltaX = SWIPE_DISTANCE / 2)
move(deltaX = SWIPE_DISTANCE)
diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml
index 627b92b8a779..3c1668405909 100644
--- a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml
+++ b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml
@@ -21,11 +21,8 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
+ <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/>
<corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/>
- <stroke
- android:width="1dp"
- android:color="?androidprv:attr/textColorTertiary" />
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 4a7bef9f48b9..80692f9481b7 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -72,7 +72,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/device_function_barrier"
- app:layout_constraintBottom_toTopOf="@id/related_tools_scroll"
android:drawableStart="@drawable/ic_add"
android:drawablePadding="20dp"
android:drawableTint="?android:attr/textColorPrimary"
@@ -92,24 +91,16 @@
app:barrierDirection="bottom"
app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" />
- <HorizontalScrollView
- android:id="@+id/related_tools_scroll"
+ <LinearLayout
+ android:id="@+id/related_tools_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
android:layout_marginTop="@dimen/hearing_devices_layout_margin"
- android:scrollbars="none"
- android:fillViewport="true"
+ android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@id/preset_spinner">
- <LinearLayout
- android:id="@+id/related_tools_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- </LinearLayout>
- </HorizontalScrollView>
+ app:layout_constraintTop_toBottomOf="@id/device_barrier" />
</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index ff2fbe070e0f..f5baf2aaf6dc 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -17,8 +17,8 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tool_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="top|center_horizontal"
android:focusable="true"
@@ -26,8 +26,10 @@
android:layout_weight="1">
<FrameLayout
android:id="@+id/icon_frame"
- android:layout_width="@dimen/hearing_devices_tool_icon_frame_width"
- android:layout_height="@dimen/hearing_devices_tool_icon_frame_height"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="20dp"
+ android:paddingBottom="20dp"
android:background="@drawable/qs_hearing_devices_related_tools_background"
android:focusable="false" >
<ImageView
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 16a8bc5b034f..f96a0b95945d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -82,6 +82,9 @@
<!-- The number of columns in the Dual Shade QuickSettings -->
<integer name="quick_settings_dual_shade_num_columns">4</integer>
+ <!-- The number of columns in the Split Shade QuickSettings -->
+ <integer name="quick_settings_split_shade_num_columns">4</integer>
+
<!-- Override column number for quick settings.
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
TODO (b/293252410) - change this comment/resource when flag is enabled -->
@@ -134,17 +137,19 @@
<!-- Use collapsed layout for media player in landscape QQS -->
<bool name="config_quickSettingsMediaLandscapeCollapsed">true</bool>
- <!-- For hearing devices related tool list. Need to be in ComponentName format (package/class).
- Should be activity to be launched.
- Already contains tool that holds intent: "com.android.settings.action.live_caption".
- Maximum number is 3. -->
+ <!-- Hearing devices related tool list. Each entry must be in ComponentName format
+ (package/class), indicating the specific application component to launch.
+ Already contains tool that handles intent: "com.android.settings.action.live_caption" by
+ default. You can add up to 2 additional related tools. -->
<string-array name="config_quickSettingsHearingDevicesRelatedToolName" translatable="false">
</string-array>
- <!-- The drawable resource names. If provided, it will replace the corresponding icons in
- config_quickSettingsHearingDevicesRelatedToolName. Can be empty to use original icons.
- Already contains tool that holds intent: "com.android.settings.action.live_caption".
- Maximum number is 3. -->
+ <!-- Hearing devices related tool icon list. Provide drawable resource names in the same order
+ as the component names in config_quickSettingsHearingDevicesRelatedToolName. The icons
+ should be monochrome and will be tinted according to the system's material color. Ensure
+ the number of icon resources matches the number of components specified in
+ config_quickSettingsHearingDevicesRelatedToolName. If this array is empty or the sizes
+ don't match, the original application icons will be used. -->
<string-array name="config_quickSettingsHearingDevicesRelatedToolIcon" translatable="false">
</string-array>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6c8a7403953e..209a971814f4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1798,8 +1798,6 @@
<dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen>
<dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
<dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
- <dimen name="hearing_devices_tool_icon_frame_width">94dp</dimen>
- <dimen name="hearing_devices_tool_icon_frame_height">64dp</dimen>
<dimen name="hearing_devices_tool_icon_size">28dp</dimen>
<!-- Height percentage of the parent container occupied by the communal view -->
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 158623fa80af..1978bb89b5b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -42,6 +42,7 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.Space;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@@ -52,6 +53,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
@@ -428,10 +430,16 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
} catch (Resources.NotFoundException e) {
Log.i(TAG, "No hearing devices related tool config resource");
}
- final int listSize = toolItemList.size();
- for (int i = 0; i < listSize; i++) {
+ for (int i = 0; i < toolItemList.size(); i++) {
View view = createHearingToolView(context, toolItemList.get(i));
mRelatedToolsContainer.addView(view);
+ if (i != toolItemList.size() - 1) {
+ final int spaceSize = context.getResources().getDimensionPixelSize(
+ R.dimen.hearing_devices_layout_margin);
+ Space space = new Space(context);
+ space.setLayoutParams(new LinearLayout.LayoutParams(spaceSize, 0));
+ mRelatedToolsContainer.addView(space);
+ }
}
}
@@ -492,6 +500,10 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
TextView text = view.requireViewById(R.id.tool_name);
view.setContentDescription(item.getToolName());
icon.setImageDrawable(item.getToolIcon());
+ if (item.isCustomIcon()) {
+ icon.getDrawable().mutate().setTint(Utils.getColorAttr(context,
+ com.android.internal.R.attr.materialColorOnPrimaryContainer).getDefaultColor());
+ }
text.setText(item.getToolName());
Intent intent = item.getToolIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -517,7 +529,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
return new ToolItem(
context.getString(R.string.quick_settings_hearing_devices_live_caption_title),
context.getDrawable(R.drawable.ic_volume_odi_captions),
- LIVE_CAPTION_INTENT);
+ LIVE_CAPTION_INTENT,
+ /* isCustomIcon= */ true);
}
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java
index 2006726e6847..7e4c1e5e7b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java
@@ -41,7 +41,7 @@ public class HearingDevicesToolItemParser {
private static final String SPLIT_DELIMITER = "/";
private static final String RES_TYPE = "drawable";
@VisibleForTesting
- static final int MAX_NUM = 3;
+ static final int MAX_NUM = 2;
/**
* Parses the string arrays to create a list of {@link ToolItem}.
@@ -82,7 +82,8 @@ public class HearingDevicesToolItemParser {
useCustomIcons ? iconList.get(i)
: activityInfoList.get(i).loadIcon(packageManager),
new Intent(Intent.ACTION_MAIN).setComponent(
- activityInfoList.get(i).getComponentName())
+ activityInfoList.get(i).getComponentName()),
+ useCustomIcons
));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt
index 66bb2b5e2328..ef03f0cdef79 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt
@@ -23,4 +23,5 @@ data class ToolItem(
var toolName: String = "",
var toolIcon: Drawable,
var toolIntent: Intent,
+ var isCustomIcon: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
index 2c026c0bb5ce..7337e5af51a1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
@@ -30,6 +30,7 @@ import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -74,6 +75,7 @@ constructor(
private val metricsLogger: MetricsLogger,
private val dozeLogger: DozeLogger,
private val sceneInteractor: Lazy<SceneInteractor>,
+ private val bouncerHapticPlayer: BouncerHapticPlayer,
) {
/** The bouncer action button. If `null`, the button should not be shown. */
val actionButton: Flow<BouncerActionButtonModel?> =
@@ -111,6 +113,8 @@ constructor(
BouncerActionButtonModel(
label = applicationContext.getString(R.string.lockscreen_emergency_call),
onClick = {
+ // TODO(b/373930432): haptics should be played at the UI layer -> refactor
+ bouncerHapticPlayer.playEmergencyButtonClickFeedback()
prepareToPerformAction()
dozeLogger.logEmergencyCall()
startEmergencyDialerActivity()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index d6b92115c64b..837390730c7a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -81,4 +81,11 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz
/** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */
fun playNumpadKeyFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_STANDARD)
+
+ /** Deliver MSDL feedback when clicking on the emergency button */
+ fun playEmergencyButtonClickFeedback() {
+ if (isEnabled) {
+ msdlPlayer.get().playToken(MSDLToken.KEYPRESS_RETURN)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
new file mode 100644
index 000000000000..2ce3e43389fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.display.data.repository
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Background
+import java.io.PrintWriter
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Provides per display instances of [T]. */
+interface PerDisplayStore<T> {
+
+ /**
+ * The instance for the default/main display of the device. For example, on a phone or a tablet,
+ * the default display is the internal/built-in display of the device.
+ *
+ * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
+ */
+ val defaultDisplay: T
+
+ /**
+ * Returns an instance for a specific display id.
+ *
+ * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
+ * displays.
+ */
+ fun forDisplay(displayId: Int): T
+}
+
+abstract class PerDisplayStoreImpl<T>(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+) : PerDisplayStore<T>, CoreStartable {
+
+ private val perDisplayInstances = ConcurrentHashMap<Int, T>()
+
+ /**
+ * The instance for the default/main display of the device. For example, on a phone or a tablet,
+ * the default display is the internal/built-in display of the device.
+ *
+ * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
+ */
+ override val defaultDisplay: T
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ /**
+ * Returns an instance for a specific display id.
+ *
+ * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
+ * displays.
+ */
+ override fun forDisplay(displayId: Int): T {
+ if (displayRepository.getDisplay(displayId) == null) {
+ throw IllegalArgumentException("Display with id $displayId doesn't exist.")
+ }
+ return perDisplayInstances.computeIfAbsent(displayId) {
+ createInstanceForDisplay(displayId)
+ }
+ }
+
+ abstract fun createInstanceForDisplay(displayId: Int): T
+
+ override fun start() {
+ val instanceType = instanceClass.simpleName
+ backgroundApplicationScope.launch(CoroutineName("PerDisplayStore#<$instanceType>start")) {
+ displayRepository.displayRemovalEvent.collect { removedDisplayId ->
+ val removedInstance = perDisplayInstances.remove(removedDisplayId)
+ removedInstance?.let { onDisplayRemovalAction(it) }
+ }
+ }
+ }
+
+ abstract val instanceClass: Class<T>
+
+ /**
+ * Will be called when the display associated with [instance] was removed. It allows to perform
+ * any clean up if needed.
+ */
+ open suspend fun onDisplayRemovalAction(instance: T) {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(perDisplayInstances)
+ }
+}
+
+class SingleDisplayStore<T>(defaultInstance: T) : PerDisplayStore<T> {
+ override val defaultDisplay: T = defaultInstance
+
+ override fun forDisplay(displayId: Int): T = defaultDisplay
+}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
index ca92953dca4a..1439ecde3bfd 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
@@ -22,13 +22,18 @@ import android.os.Bundle
import android.view.Gravity
import android.view.Window
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel
import com.android.systemui.res.R
-class ContextualEduDialog(context: Context, private val model: ContextualEduToastViewModel) :
- Dialog(context) {
+class ContextualEduDialog(
+ context: Context,
+ private val model: ContextualEduToastViewModel,
+ private val accessibilityManager: AccessibilityManager,
+) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
setUpWindowProperties()
setWindowPosition()
@@ -36,6 +41,7 @@ class ContextualEduDialog(context: Context, private val model: ContextualEduToas
window?.setTitle(context.getString(R.string.contextual_education_dialog_title))
setContentView(R.layout.contextual_edu_dialog)
setContent()
+ sendAccessibilityEvent()
super.onCreate(savedInstanceState)
}
@@ -44,10 +50,30 @@ class ContextualEduDialog(context: Context, private val model: ContextualEduToas
findViewById<ImageView>(R.id.edu_icon)?.let { it.setImageResource(model.icon) }
}
+ private fun sendAccessibilityEvent() {
+ if (!accessibilityManager.isEnabled) {
+ return
+ }
+
+ // It is a toast-like dialog which is unobtrusive and not focusable. So it needs to call
+ // accessibilityManager.sendAccessibilityEvent explicitly to announce the message.
+ accessibilityManager.sendAccessibilityEvent(
+ AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT).apply {
+ text.add(model.message)
+ }
+ )
+ }
+
private fun setUpWindowProperties() {
window?.apply {
requestFeature(Window.FEATURE_NO_TITLE)
setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)
+ // NOT_TOUCH_MODAL allows users to interact with background elements and NOT_FOCUSABLE
+ // avoids changing the existing focus when dialog is shown.
+ addFlags(
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ )
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
setBackgroundDrawableResource(android.R.color.transparent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index 913ecdd4aadc..1996efa14d7c 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -25,6 +25,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.UserHandle
+import android.view.accessibility.AccessibilityManager
import androidx.core.app.NotificationCompat
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -64,12 +65,13 @@ constructor(
context: Context,
viewModel: ContextualEduViewModel,
notificationManager: NotificationManager,
+ accessibilityManager: AccessibilityManager,
) : this(
applicationScope,
viewModel,
context,
notificationManager,
- createDialog = { model -> ContextualEduDialog(context, model) },
+ createDialog = { model -> ContextualEduDialog(context, model, accessibilityManager) },
)
var dialog: Dialog? = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index d7f96b55c4a3..6ecbc6175e40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -297,20 +297,39 @@ constructor(
val isKeyguardVisible: Flow<Boolean> =
combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded }
+ /**
+ * Event types that affect whether secure camera is active. Only used by [isSecureCameraActive].
+ */
+ private enum class SecureCameraRelatedEventType {
+ KeyguardBecameVisible,
+ PrimaryBouncerBecameVisible,
+ SecureCameraLaunched,
+ }
+
/** Whether camera is launched over keyguard. */
- val isSecureCameraActive: Flow<Boolean> by lazy {
- combine(isKeyguardVisible, primaryBouncerShowing, onCameraLaunchDetected) {
- isKeyguardVisible,
- isPrimaryBouncerShowing,
- cameraLaunchEvent ->
- when {
- isKeyguardVisible -> false
- isPrimaryBouncerShowing -> false
- else -> cameraLaunchEvent.type == CameraLaunchType.POWER_DOUBLE_TAP
+ val isSecureCameraActive: Flow<Boolean> =
+ merge(
+ onCameraLaunchDetected
+ .filter { it.type == CameraLaunchType.POWER_DOUBLE_TAP }
+ .map { SecureCameraRelatedEventType.SecureCameraLaunched },
+ isKeyguardVisible
+ .filter { it }
+ .map { SecureCameraRelatedEventType.KeyguardBecameVisible },
+ primaryBouncerShowing
+ .filter { it }
+ .map { SecureCameraRelatedEventType.PrimaryBouncerBecameVisible },
+ )
+ .map {
+ when (it) {
+ SecureCameraRelatedEventType.SecureCameraLaunched -> true
+ // When secure camera is closed, either the keyguard or the primary bouncer will
+ // have to show, so those events tell us that secure camera is no longer active.
+ SecureCameraRelatedEventType.KeyguardBecameVisible -> false
+ SecureCameraRelatedEventType.PrimaryBouncerBecameVisible -> false
}
}
.onStart { emit(false) }
- }
+ .distinctUntilChanged()
/** The approximate location on the screen of the fingerprint sensor, if one is available. */
val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
index 082f622248a1..a9205c27216d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepository.kt
@@ -19,40 +19,31 @@ 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.shade.shared.flag.DualShade
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.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class QSColumnsRepository
@Inject
constructor(
- @Application scope: CoroutineScope,
@Main private val resources: Resources,
configurationRepository: ConfigurationRepository,
) {
- val columns: StateFlow<Int> =
- if (DualShade.isEnabled) {
- flowOf(resources.getInteger(R.integer.quick_settings_dual_shade_num_columns))
- } else {
- configurationRepository.onConfigurationChange.emitOnStart().mapLatest {
- resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns)
- }
- }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns),
- )
+ val splitShadeColumns: Flow<Int> =
+ flowOf(resources.getInteger(R.integer.quick_settings_split_shade_num_columns))
+ val dualShadeColumns: Flow<Int> =
+ flowOf(resources.getInteger(R.integer.quick_settings_dual_shade_num_columns))
+ val columns: Flow<Int> =
+ configurationRepository.onConfigurationChange.emitOnStart().mapLatest {
+ resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns)
+ }
+ val defaultColumns: Int =
+ resources.getInteger(R.integer.quick_settings_infinite_grid_num_columns)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
index 9b45c568dcad..11ea60e874b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractor.kt
@@ -17,11 +17,35 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.panels.data.repository.QSColumnsRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
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.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
-class QSColumnsInteractor @Inject constructor(repo: QSColumnsRepository) {
- val columns: StateFlow<Int> = repo.columns
+class QSColumnsInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ repo: QSColumnsRepository,
+ shadeInteractor: ShadeInteractor,
+) {
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val columns: StateFlow<Int> =
+ shadeInteractor.shadeMode
+ .flatMapLatest {
+ when (it) {
+ ShadeMode.Dual -> repo.dualShadeColumns
+ ShadeMode.Split -> repo.splitShadeColumns
+ ShadeMode.Single -> repo.columns
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), repo.defaultColumns)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 801a0ce4b744..537b56bccae8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -39,7 +39,7 @@ constructor(@Main private val resources: Resources, val theme: Resources.Theme)
val loadedIcon: Icon.Loaded =
when (val dataIcon = data.icon) {
is Icon.Resource -> {
- if (iconRes != dataIcon.res) {
+ if (data.iconResId != dataIcon.res) {
Log.wtf(
"ModesTileMapper",
"Icon.Resource.res & iconResId are not identical",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 2c94632abcda..6201ca553398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.core
import android.app.Fragment
+import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
import com.android.systemui.fragments.FragmentHostManager
@@ -23,9 +24,11 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
import com.android.systemui.statusbar.window.StatusBarWindowController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -57,7 +60,7 @@ interface StatusBarInitializer : CoreStartable {
* Can be used to retrieve dependencies from that scope, including the status bar root
* view.
*/
- fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
+ fun onStatusBarViewInitialized(component: HomeStatusBarComponent)
}
interface OnStatusBarViewUpdatedListener {
@@ -77,9 +80,11 @@ class StatusBarInitializerImpl
constructor(
@Assisted private val statusBarWindowController: StatusBarWindowController,
private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
+ private val statusBarRootFactory: StatusBarRootFactory,
+ private val componentFactory: HomeStatusBarComponent.Factory,
private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
) : StatusBarInitializer {
- private var component: StatusBarFragmentComponent? = null
+ private var component: HomeStatusBarComponent? = null
@get:VisibleForTesting
var initialized = false
@@ -109,21 +114,57 @@ constructor(
}
private fun doStart() {
+ if (StatusBarSimpleFragment.isEnabled) doComposeStart() else doLegacyStart()
+ }
+
+ /**
+ * Stand up the [PhoneStatusBarView] in a compose root. There will be no
+ * [CollapsedStatusBarFragment] in this mode
+ */
+ private fun doComposeStart() {
+ initialized = true
+ val statusBarRoot =
+ statusBarRootFactory.create(statusBarWindowController.backgroundView as ViewGroup) { cv
+ ->
+ val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
+ component =
+ componentFactory.create(phoneStatusBarView).also { component ->
+ // CollapsedStatusBarFragment used to be responsible initializting
+ component.init()
+
+ statusBarViewUpdatedListener?.onStatusBarViewUpdated(
+ component.phoneStatusBarViewController,
+ component.phoneStatusBarTransitions,
+ )
+
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(component)
+ }
+ }
+ }
+
+ // Add the new compose view to the hierarchy because we don't use fragment transactions
+ // anymore
+ val windowBackgroundView = statusBarWindowController.backgroundView as ViewGroup
+ windowBackgroundView.addView(statusBarRoot)
+ }
+
+ private fun doLegacyStart() {
initialized = true
statusBarWindowController.fragmentHostManager
.addTagListener(
CollapsedStatusBarFragment.TAG,
object : FragmentHostManager.FragmentListener {
override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
- val statusBarFragmentComponent =
- (fragment as CollapsedStatusBarFragment).statusBarFragmentComponent
+ component =
+ (fragment as CollapsedStatusBarFragment).homeStatusBarComponent
?: throw IllegalStateException()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
- statusBarFragmentComponent.phoneStatusBarViewController,
- statusBarFragmentComponent.phoneStatusBarTransitions,
+ component!!.phoneStatusBarViewController,
+ component!!.phoneStatusBarTransitions,
)
creationListeners.forEach { listener ->
- listener.onStatusBarViewInitialized(statusBarFragmentComponent)
+ listener.onStatusBarViewInitialized(component!!)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 6c3802676f26..041f0b0fdf93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -16,88 +16,52 @@
package com.android.systemui.statusbar.core
-import android.view.Display
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
-import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Provides per display instances of [StatusBarInitializer]. */
-interface StatusBarInitializerStore {
- /**
- * The instance for the default/main display of the device. For example, on a phone or a tablet,
- * the default display is the internal/built-in display of the device.
- *
- * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
- */
- val defaultDisplay: StatusBarInitializer
-
- /**
- * Returns an instance for a specific display id.
- *
- * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
- * displays.
- */
- fun forDisplay(displayId: Int): StatusBarInitializer
-}
+interface StatusBarInitializerStore : PerDisplayStore<StatusBarInitializer>
@SysUISingleton
class MultiDisplayStatusBarInitializerStore
@Inject
constructor(
- @Background private val backgroundApplicationScope: CoroutineScope,
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
private val factory: StatusBarInitializer.Factory,
- private val displayRepository: DisplayRepository,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
-) : StatusBarInitializerStore, CoreStartable {
+) :
+ StatusBarInitializerStore,
+ PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
init {
StatusBarConnectedDisplays.assertInNewMode()
}
- private val perDisplayInitializers = ConcurrentHashMap<Int, StatusBarInitializer>()
-
- override val defaultDisplay: StatusBarInitializer
- get() = forDisplay(Display.DEFAULT_DISPLAY)
-
- override fun forDisplay(displayId: Int): StatusBarInitializer {
- if (displayRepository.getDisplay(displayId) == null) {
- throw IllegalArgumentException("Display with id $displayId doesn't exist.")
- }
- return perDisplayInitializers.computeIfAbsent(displayId) {
- factory.create(
- statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
- )
- }
+ override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
+ return factory.create(
+ statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+ )
}
- override fun start() {
- backgroundApplicationScope.launch(
- CoroutineName("MultiDisplayStatusBarInitializerStore#start")
- ) {
- displayRepository.displayRemovalEvent.collect { removedDisplayId ->
- perDisplayInitializers.remove(removedDisplayId)
- }
- }
- }
+ override val instanceClass = StatusBarInitializer::class.java
}
@SysUISingleton
class SingleDisplayStatusBarInitializerStore
@Inject
-constructor(private val defaultInstance: StatusBarInitializer) : StatusBarInitializerStore {
+constructor(defaultInitializer: StatusBarInitializer) :
+ StatusBarInitializerStore,
+ PerDisplayStore<StatusBarInitializer> by SingleDisplayStore(defaultInitializer) {
init {
StatusBarConnectedDisplays.assertInLegacyMode()
}
-
- override val defaultDisplay: StatusBarInitializer = defaultInstance
-
- override fun forDisplay(displayId: Int): StatusBarInitializer = defaultInstance
}
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 47e6c57a5ca7..5e59745cad29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -169,11 +169,11 @@ constructor(
}
private fun createAndAddWindow() {
- initializeStatusBarFragment()
+ initializeStatusBarRootView()
statusBarWindowController.attach()
}
- private fun initializeStatusBarFragment() {
+ private fun initializeStatusBarRootView() {
statusBarInitializer.statusBarViewUpdatedListener =
object : StatusBarInitializer.OnStatusBarViewUpdatedListener {
override fun onStatusBarViewUpdated(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 5aad11fe1034..f6f4503b210a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,17 +17,20 @@
package com.android.systemui.statusbar.dagger
import android.content.Context
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
+import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
@@ -108,5 +111,16 @@ abstract class StatusBarModule {
fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("OngoingCall", 75)
}
+
+ @Provides
+ @SysUISingleton
+ fun contentInsetsProvider(
+ factory: StatusBarContentInsetsProviderImpl.Factory,
+ context: Context,
+ configurationController: ConfigurationController,
+ sysUICutoutProvider: SysUICutoutProvider,
+ ): StatusBarContentInsetsProvider {
+ return factory.create(context, configurationController, sysUICutoutProvider)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 088c86df8437..44bee1d784fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -36,7 +36,7 @@ import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import dagger.assisted.Assisted
@@ -174,7 +174,7 @@ constructor(
private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect()))
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+ override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {
val statusBarBoundsProvider = component.boundsProvider
val listener =
object : StatusBarBoundsProvider.BoundsChangeListener {
@@ -196,10 +196,9 @@ constructor(
/** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */
private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> =
- combine(
- _originalStatusBarAttributes,
- _statusBarBounds,
- ) { originalAttributes, statusBarBounds ->
+ combine(_originalStatusBarAttributes, _statusBarBounds) {
+ originalAttributes,
+ statusBarBounds ->
if (originalAttributes == null) {
null
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 154be1f96e8b..2c9fa25d8535 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -20,7 +20,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.core.StatusBarInitializer
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -56,7 +56,7 @@ constructor(
defaultDisplay.start()
}
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+ override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {
defaultDisplay.onStatusBarViewInitialized(component)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8f94c0656836..d0f4b6f4a4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
+import static com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarModule.OPERATOR_NAME_FRAME_VIEW;
import android.graphics.Rect;
import android.util.MathUtils;
@@ -44,7 +44,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -63,7 +63,7 @@ import javax.inject.Named;
* Controls the appearance of heads up notifications in the icon area and the header itself.
* It also controls the roundness of the heads up notifications and the pulsing notifications.
*/
-@StatusBarFragmentScope
+@HomeStatusBarScope
public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
implements OnHeadsUpChangedListener,
DarkIconDispatcher.DarkReceiver,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
index 7c871e183740..5acc3a684515 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.phone;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
-import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.LIGHTS_OUT_NOTIF_VIEW;
+import static com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarModule.LIGHTS_OUT_NOTIF_VIEW;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,7 +37,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -51,7 +51,7 @@ import javax.inject.Named;
* This controller shows and hides the notification dot in the status bar to indicate
* whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
*/
-@StatusBarFragmentScope
+@HomeStatusBarScope
public class LegacyLightsOutNotifController extends ViewController<View> {
private final CommandQueue mCommandQueue;
private final NotifLiveDataStore mNotifDataStore;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
index 00b08f097e97..3ac0bac95d68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -18,10 +18,10 @@ package com.android.systemui.statusbar.phone
import android.graphics.Rect
import android.view.View
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarModule.END_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarModule.START_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope
import com.android.systemui.util.ListenerSet
import com.android.systemui.util.boundsOnScreen
import javax.inject.Inject
@@ -33,13 +33,13 @@ import javax.inject.Named
* This is distinct from [StatusBarContentInsetsProvider], which provides the bounds of full status
* bar after accounting for system insets.
*/
-@StatusBarFragmentScope
+@HomeStatusBarScope
class StatusBarBoundsProvider
@Inject
constructor(
@Named(START_SIDE_CONTENT) private val startSideContent: View,
@Named(END_SIDE_CONTENT) private val endSideContent: View,
-) : StatusBarFragmentComponent.Startable {
+) : HomeStatusBarComponent.Startable {
interface BoundsChangeListener {
fun onStatusBarBoundsChanged(bounds: BoundsPair)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 613efaa148f5..c6f6bd90fce6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -34,10 +34,10 @@ import com.android.systemui.Dumpable
import com.android.systemui.StatusBarInsetsCommand
import com.android.systemui.SysUICutoutInformation
import com.android.systemui.SysUICutoutProvider
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl.CacheKey
import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
@@ -47,9 +47,11 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
import com.android.systemui.util.leak.RotationUtils.getExactRotation
import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import java.io.PrintWriter
import java.lang.Math.max
-import javax.inject.Inject
/**
* Encapsulates logic that can solve for the left/right insets required for the status bar contents.
@@ -64,19 +66,87 @@ import javax.inject.Inject
*
* NOTE: This class is not threadsafe
*/
-@SysUISingleton
-class StatusBarContentInsetsProvider
-@Inject
+interface StatusBarContentInsetsProvider :
+ CallbackController<StatusBarContentInsetsChangedListener> {
+
+ /**
+ * Some views may need to care about whether or not the current top display cutout is located in
+ * the corner rather than somewhere in the center. In the case of a corner cutout, the status
+ * bar area is contiguous.
+ */
+ fun currentRotationHasCornerCutout(): Boolean
+
+ /**
+ * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy
+ * dot in the coordinates relative to the given rotation.
+ *
+ * @param rotation the rotation for which the bounds are required. This is an absolute value
+ * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
+ * which this method is called)
+ */
+ fun getBoundingRectForPrivacyChipForRotation(
+ @Rotation rotation: Int,
+ displayCutout: DisplayCutout?,
+ ): Rect
+
+ /**
+ * Calculate the distance from the left, right and top edges of the screen to the status bar
+ * content area. This differs from the content area rects in that these values can be used
+ * directly as padding.
+ *
+ * @param rotation the target rotation for which to calculate insets
+ */
+ fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets
+
+ /**
+ * Calculate the insets for the status bar content in the device's current rotation
+ *
+ * @see getStatusBarContentAreaForRotation
+ */
+ fun getStatusBarContentInsetsForCurrentRotation(): Insets
+
+ /**
+ * Calculates the area of the status bar contents invariant of the current device rotation, in
+ * the target rotation's coordinates
+ *
+ * @param rotation the rotation for which the bounds are required. This is an absolute value
+ * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
+ * which this method is called)
+ */
+ fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect
+
+ /** Get the status bar content area for the given rotation, in absolute bounds */
+ fun getStatusBarContentAreaForCurrentRotation(): Rect
+
+ fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int
+
+ interface Factory {
+ fun create(
+ context: Context,
+ configurationController: ConfigurationController,
+ sysUICutoutProvider: SysUICutoutProvider,
+ ): StatusBarContentInsetsProvider
+ }
+}
+
+class StatusBarContentInsetsProviderImpl
+@AssistedInject
constructor(
- val context: Context,
- val configurationController: ConfigurationController,
+ @Assisted val context: Context,
+ @Assisted val configurationController: ConfigurationController,
val dumpManager: DumpManager,
val commandRegistry: CommandRegistry,
- val sysUICutoutProvider: SysUICutoutProvider,
-) :
- CallbackController<StatusBarContentInsetsChangedListener>,
- ConfigurationController.ConfigurationListener,
- Dumpable {
+ @Assisted val sysUICutoutProvider: SysUICutoutProvider,
+) : StatusBarContentInsetsProvider, ConfigurationController.ConfigurationListener, Dumpable {
+
+ @AssistedFactory
+ interface Factory : StatusBarContentInsetsProvider.Factory {
+ override fun create(
+ context: Context,
+ configurationController: ConfigurationController,
+ sysUICutoutProvider: SysUICutoutProvider,
+ ): StatusBarContentInsetsProviderImpl
+ }
// Limit cache size as potentially we may connect large number of displays
// (e.g. network displays)
@@ -95,7 +165,7 @@ constructor(
object : StatusBarInsetsCommand.Callback {
override fun onExecute(
command: StatusBarInsetsCommand,
- printWriter: PrintWriter
+ printWriter: PrintWriter,
) {
executeCommand(command, printWriter)
}
@@ -133,12 +203,7 @@ constructor(
listeners.forEach { it.onStatusBarContentInsetsChanged() }
}
- /**
- * Some views may need to care about whether or not the current top display cutout is located in
- * the corner rather than somewhere in the center. In the case of a corner cutout, the status
- * bar area is contiguous.
- */
- fun currentRotationHasCornerCutout(): Boolean {
+ override fun currentRotationHasCornerCutout(): Boolean {
val cutout = checkNotNull(context.display).cutout ?: return false
val topBounds = cutout.boundingRectTop
@@ -148,17 +213,9 @@ constructor(
return topBounds.left <= 0 || topBounds.right >= point.x
}
- /**
- * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy
- * dot in the coordinates relative to the given rotation.
- *
- * @param rotation the rotation for which the bounds are required. This is an absolute value
- * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
- * which this method is called)
- */
- fun getBoundingRectForPrivacyChipForRotation(
+ override fun getBoundingRectForPrivacyChipForRotation(
@Rotation rotation: Int,
- displayCutout: DisplayCutout?
+ displayCutout: DisplayCutout?,
): Rect {
val key = getCacheKey(rotation, displayCutout)
var insets = insetsCache[key]
@@ -176,14 +233,7 @@ constructor(
return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl)
}
- /**
- * Calculate the distance from the left, right and top edges of the screen to the status bar
- * content area. This differs from the content area rects in that these values can be used
- * directly as padding.
- *
- * @param rotation the target rotation for which to calculate insets
- */
- fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
+ override fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
val displayCutout = sysUICutout?.cutout
@@ -202,31 +252,17 @@ constructor(
rotation,
sysUICutout,
getResourcesForRotation(rotation, context),
- key
+ key,
)
Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
}
- /**
- * Calculate the insets for the status bar content in the device's current rotation
- *
- * @see getStatusBarContentAreaForRotation
- */
- fun getStatusBarContentInsetsForCurrentRotation(): Insets {
+ override fun getStatusBarContentInsetsForCurrentRotation(): Insets {
return getStatusBarContentInsetsForRotation(getExactRotation(context))
}
- /**
- * Calculates the area of the status bar contents invariant of the current device rotation, in
- * the target rotation's coordinates
- *
- * @param rotation the rotation for which the bounds are required. This is an absolute value
- * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from
- * which this method is called)
- */
- @JvmOverloads
- fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
+ override fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
@@ -235,12 +271,11 @@ constructor(
rotation,
sysUICutout,
getResourcesForRotation(rotation, context),
- key
+ key,
)
}
- /** Get the status bar content area for the given rotation, in absolute bounds */
- fun getStatusBarContentAreaForCurrentRotation(): Rect {
+ override fun getStatusBarContentAreaForCurrentRotation(): Rect {
val rotation = getExactRotation(context)
return getStatusBarContentAreaForRotation(rotation)
}
@@ -249,7 +284,7 @@ constructor(
@Rotation targetRotation: Int,
sysUICutout: SysUICutoutInformation?,
rotatedResources: Resources,
- key: CacheKey
+ key: CacheKey,
): Rect {
return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources).also {
insetsCache.put(key, it)
@@ -259,7 +294,7 @@ constructor(
private fun getCalculatedAreaForRotation(
sysUICutout: SysUICutoutInformation?,
@Rotation targetRotation: Int,
- rotatedResources: Resources
+ rotatedResources: Resources,
): Rect {
val currentRotation = getExactRotation(context)
@@ -299,7 +334,7 @@ constructor(
configurationController.isLayoutRtl,
dotWidth,
bottomAlignedMargin,
- statusBarContentHeight
+ statusBarContentHeight,
)
}
@@ -349,7 +384,7 @@ constructor(
return resources.getDimensionPixelSize(dimenRes)
}
- fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
+ override fun getStatusBarPaddingTop(@Rotation rotation: Int?): Int {
val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources
return res.getDimensionPixelSize(R.dimen.status_bar_padding_top)
}
@@ -364,13 +399,13 @@ constructor(
CacheKey(
rotation = rotation,
displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds),
- displayCutout = displayCutout
+ displayCutout = displayCutout,
)
private data class CacheKey(
@Rotation val rotation: Int,
val displaySize: Rect,
- val displayCutout: DisplayCutout?
+ val displayCutout: DisplayCutout?,
)
}
@@ -395,21 +430,21 @@ fun getPrivacyChipBoundingRectForInsets(
contentRect: Rect,
dotWidth: Int,
chipWidth: Int,
- isRtl: Boolean
+ isRtl: Boolean,
): Rect {
return if (isRtl) {
Rect(
contentRect.left - dotWidth,
contentRect.top,
contentRect.left + chipWidth,
- contentRect.bottom
+ contentRect.bottom,
)
} else {
Rect(
contentRect.right - chipWidth,
contentRect.top,
contentRect.right + dotWidth,
- contentRect.bottom
+ contentRect.bottom,
)
}
}
@@ -443,7 +478,7 @@ fun calculateInsetsForRotationWithRotatedResources(
isRtl: Boolean,
dotWidth: Int,
bottomAlignedMargin: Int,
- statusBarContentHeight: Int
+ statusBarContentHeight: Int,
): Rect {
/*
TODO: Check if this is ever used for devices with no rounded corners
@@ -467,7 +502,7 @@ fun calculateInsetsForRotationWithRotatedResources(
targetRotation,
currentRotation,
bottomAlignedMargin,
- statusBarContentHeight
+ statusBarContentHeight,
)
}
@@ -503,7 +538,7 @@ private fun getStatusBarContentBounds(
@Rotation targetRotation: Int,
@Rotation currentRotation: Int,
bottomAlignedMargin: Int,
- statusBarContentHeight: Int
+ statusBarContentHeight: Int,
): Rect {
val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight)
@@ -597,7 +632,7 @@ private val DisplayCutout.boundingRectsLeftRightTop
private fun getInsetTop(
bottomAlignedMargin: Int,
statusBarContentHeight: Int,
- statusBarHeight: Int
+ statusBarHeight: Int,
): Int {
val bottomAlignmentEnabled = bottomAlignedMargin >= 0
if (!bottomAlignmentEnabled) {
@@ -610,7 +645,7 @@ private fun getInsetTop(
private fun sbRect(
@Rotation relativeRotation: Int,
sbHeight: Int,
- displaySize: Pair<Int, Int>
+ displaySize: Pair<Int, Int>,
): Rect {
val w = displaySize.first
val h = displaySize.second
@@ -626,7 +661,7 @@ private fun shareShortEdge(
sbRect: Rect,
cutoutRect: Rect,
currentWidth: Int,
- currentHeight: Int
+ currentHeight: Int,
): Boolean {
if (currentWidth < currentHeight) {
// Check top/bottom edges by extending the width of the display cutout rect and checking
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
index 25b8bfe0da25..1afe416c43c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -21,7 +21,7 @@ import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SE
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_WARNING;
-import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_VIEW;
+import static com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarModule.OPERATOR_NAME_VIEW;
import android.annotation.NonNull;
import android.os.Bundle;
@@ -32,7 +32,7 @@ import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.util.ViewController;
@@ -48,7 +48,7 @@ import javax.inject.Named;
* This class extends ViewController not because it controls a specific view, but because we want it
* to get torn down and re-created in line with the view's lifecycle.
*/
-@StatusBarFragmentScope
+@HomeStatusBarScope
public class StatusBarDemoMode extends ViewController<View> implements DemoMode {
private final Clock mClockView;
private final View mOperatorNameView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index d868519ee9e2..37c8c637c804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -66,15 +66,15 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarLocation;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.phone.ui.DarkIconManager;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -115,7 +115,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public static final int FADE_IN_DELAY = 50;
private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1;
private static final int SOURCE_OTHER = 2;
- private StatusBarFragmentComponent mStatusBarFragmentComponent;
+ private HomeStatusBarComponent mHomeStatusBarComponent;
private PhoneStatusBarView mStatusBar;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
@@ -134,7 +134,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private StatusBarVisibilityModel mLastModifiedVisibility =
StatusBarVisibilityModel.createDefaultModel();
private DarkIconManager mDarkIconManager;
- private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
+ private final HomeStatusBarComponent.Factory mHomeStatusBarComponentFactory;
private final CommandQueue mCommandQueue;
private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
@@ -143,8 +143,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
- private final CollapsedStatusBarViewModel mCollapsedStatusBarViewModel;
- private final CollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder;
+ private final HomeStatusBarViewModel mHomeStatusBarViewModel;
+ private final HomeStatusBarViewBinder mHomeStatusBarViewBinder;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final DarkIconManager.Factory mDarkIconManagerFactory;
private final SecureSettings mSecureSettings;
@@ -239,14 +239,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Inject
public CollapsedStatusBarFragment(
- StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
+ HomeStatusBarComponent.Factory homeStatusBarComponentFactory,
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
- CollapsedStatusBarViewModel collapsedStatusBarViewModel,
- CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
+ HomeStatusBarViewModel homeStatusBarViewModel,
+ HomeStatusBarViewBinder homeStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
KeyguardStateController keyguardStateController,
PanelExpansionInteractor panelExpansionInteractor,
@@ -262,13 +262,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
StatusBarWindowStateController statusBarWindowStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DemoModeController demoModeController) {
- mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
+ mHomeStatusBarComponentFactory = homeStatusBarComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarIconController = statusBarIconController;
- mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
- mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder;
+ mHomeStatusBarViewModel = homeStatusBarViewModel;
+ mHomeStatusBarViewBinder = homeStatusBarViewBinder;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mDarkIconManagerFactory = darkIconManagerFactory;
mKeyguardStateController = keyguardStateController;
@@ -335,11 +335,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDumpManager.registerDumpable(getDumpableName(), this);
- mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(
+ mHomeStatusBarComponent = mHomeStatusBarComponentFactory.create(
(PhoneStatusBarView) getView());
- mStatusBarFragmentComponent.init();
+ mHomeStatusBarComponent.init();
mStartableStates.clear();
- for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ for (Startable startable : mHomeStatusBarComponent.getStartables()) {
mStartableStates.put(startable, Startable.State.STARTING);
startable.start();
mStartableStates.put(startable, Startable.State.STARTED);
@@ -371,8 +371,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
- mCollapsedStatusBarViewBinder.bind(
- mStatusBar, mCollapsedStatusBarViewModel, mStatusBarVisibilityChangeListener);
+ mHomeStatusBarViewBinder.bind(
+ mStatusBar, mHomeStatusBarViewModel, mStatusBarVisibilityChangeListener);
}
private String getDumpableName() {
@@ -474,7 +474,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
- for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ for (Startable startable : mHomeStatusBarComponent.getStartables()) {
mStartableStates.put(startable, Startable.State.STOPPING);
startable.stop();
mStartableStates.put(startable, Startable.State.STOPPED);
@@ -515,8 +515,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
* fragment functionality and we won't need to expose it here anymore.
*/
@Nullable
- public StatusBarFragmentComponent getStatusBarFragmentComponent() {
- return mStatusBarFragmentComponent;
+ public HomeStatusBarComponent getHomeStatusBarComponent() {
+ return mHomeStatusBarComponent;
}
private StatusBarVisibilityChangeListener mStatusBarVisibilityChangeListener =
@@ -622,7 +622,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
// TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
boolean headsUpVisible =
- mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
+ mHomeStatusBarComponent.getHeadsUpAppearanceController().shouldBeVisible();
if (SceneContainerFlag.isEnabled()) {
// With the scene container, only use the value calculated by the view model to
@@ -757,7 +757,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
// transition to occluding to finish before allowing us to potentially show the status bar
// again. (This status bar is always hidden on keyguard, so it's safe to continue hiding it
// during this transition.) See b/273314977.
- if (mCollapsedStatusBarViewModel.isTransitioningFromLockscreenToOccluded().getValue()) {
+ if (mHomeStatusBarViewModel.isTransitioningFromLockscreenToOccluded().getValue()) {
return true;
}
@@ -997,7 +997,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
pw.println("mHasPrimaryOngoingActivity=" + mHasPrimaryOngoingActivity);
pw.println("mHasSecondaryOngoingActivity=" + mHasSecondaryOngoingActivity);
pw.println("mAnimationsEnabled=" + mAnimationsEnabled);
- StatusBarFragmentComponent component = mStatusBarFragmentComponent;
+ HomeStatusBarComponent component = mHomeStatusBarComponent;
if (component == null) {
pw.println("StatusBarFragmentComponent is null");
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
index 55af0e3d65a3..94006f6cf1a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
@@ -20,7 +20,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fragments.FragmentService
import com.android.systemui.qs.QSFragmentStartable
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -37,7 +37,7 @@ class CollapsedStatusBarFragmentStartable
@Inject
constructor(
private val fragmentService: FragmentService,
- private val collapsedstatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>
+ private val collapsedstatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
) : CoreStartable {
override fun start() {
fragmentService.addFragmentInstantiationProvider(
@@ -47,7 +47,7 @@ constructor(
}
}
-@Module(subcomponents = [StatusBarFragmentComponent::class])
+@Module(subcomponents = [HomeStatusBarComponent::class])
interface CollapsedStatusBarFragmentStartableModule {
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
index 96faa359d43e..d4cb625d3722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarComponent.java
@@ -42,27 +42,29 @@ import java.util.Set;
* fragment is recreated.
*
* Anything that depends on {@link CollapsedStatusBarFragment} or {@link PhoneStatusBarView}
- * should be included here or in {@link StatusBarFragmentModule}.
+ * should be included here or in {@link HomeStatusBarModule}.
*/
-
@Subcomponent(modules = {
- StatusBarFragmentModule.class,
+ HomeStatusBarModule.class,
StatusBarStartablesModule.class
})
-@StatusBarFragmentScope
-public interface StatusBarFragmentComponent {
+@HomeStatusBarScope
+public interface HomeStatusBarComponent {
/** Simple factory. */
@Subcomponent.Factory
interface Factory {
- StatusBarFragmentComponent create(
+ /** */
+ HomeStatusBarComponent create(
@BindsInstance @RootView PhoneStatusBarView phoneStatusBarView);
}
/**
- * Performs initialization logic after {@link StatusBarFragmentComponent} has been constructed.
+ * Performs initialization logic after {@link HomeStatusBarComponent} has been constructed.
*/
interface Startable {
+ /** */
void start();
+ /** */
void stop();
enum State {
@@ -86,32 +88,32 @@ public interface StatusBarFragmentComponent {
}
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
BatteryMeterViewController getBatteryMeterViewController();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@RootView
PhoneStatusBarView getPhoneStatusBarView();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
PhoneStatusBarViewController getPhoneStatusBarViewController();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
HeadsUpAppearanceController getHeadsUpAppearanceController();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
LegacyLightsOutNotifController getLegacyLightsOutNotifController();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
StatusBarDemoMode getStatusBarDemoMode();
/** */
- @StatusBarFragmentScope
+ @HomeStatusBarScope
PhoneStatusBarTransitions getPhoneStatusBarTransitions();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java
index cf877a741d6b..05b3238187a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarModule.java
@@ -28,7 +28,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import dagger.Module;
@@ -38,9 +37,9 @@ import java.util.Optional;
import javax.inject.Named;
-/** Dagger module for {@link StatusBarFragmentComponent}. */
+/** Dagger module for {@link HomeStatusBarComponent}. */
@Module
-public interface StatusBarFragmentModule {
+public interface HomeStatusBarModule {
String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
String OPERATOR_NAME_VIEW = "operator_name_view";
@@ -50,21 +49,21 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static BatteryMeterView provideBatteryMeterView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.battery);
}
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static StatusBarLocation getStatusBarLocation() {
return StatusBarLocation.HOME;
}
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@Named(START_SIDE_CONTENT)
static View startSideContent(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.status_bar_start_side_content);
@@ -72,7 +71,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@Named(END_SIDE_CONTENT)
static View endSideContent(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.status_bar_end_side_content);
@@ -80,7 +79,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@Named(LIGHTS_OUT_NOTIF_VIEW)
static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.notification_lights_out);
@@ -88,7 +87,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@Named(OPERATOR_NAME_VIEW)
static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
@@ -98,7 +97,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
@Named(OPERATOR_NAME_FRAME_VIEW)
static Optional<View> provideOperatorFrameNameView(@RootView PhoneStatusBarView view) {
return Optional.ofNullable(view.findViewById(R.id.operator_name_frame));
@@ -106,14 +105,14 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static Clock provideClock(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.clock);
}
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
@RootView PhoneStatusBarView phoneStatusBarView) {
@@ -123,7 +122,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static PhoneStatusBarTransitions providePhoneStatusBarTransitions(
@RootView PhoneStatusBarView view,
StatusBarWindowControllerStore statusBarWindowControllerStore) {
@@ -133,7 +132,7 @@ public interface StatusBarFragmentModule {
/** */
@Provides
- @StatusBarFragmentScope
+ @HomeStatusBarScope
static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.heads_up_status_bar_view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarScope.java
index 96cff5960d68..2b1edddacd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentScope.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/HomeStatusBarScope.java
@@ -24,9 +24,9 @@ import java.lang.annotation.Retention;
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the {@link StatusBarFragmentComponent}.
+ * Scope annotation for singleton items within the {@link HomeStatusBarComponent}.
*/
@Documented
@Retention(RUNTIME)
@Scope
-public @interface StatusBarFragmentScope {}
+public @interface HomeStatusBarScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
index 9003d13df0a0..ba9181436fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
@@ -28,5 +28,5 @@ internal interface StatusBarStartablesModule {
@IntoSet
fun statusBarBoundsCalculator(
statusBarBoundsProvider: StatusBarBoundsProvider
- ): StatusBarFragmentComponent.Startable
+ ): HomeStatusBarComponent.Startable
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 48500499f537..935b1012be31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -48,10 +48,10 @@ import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBase
import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModelImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -131,14 +131,10 @@ abstract class StatusBarPipelineModule {
abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable
@Binds
- abstract fun collapsedStatusBarViewModel(
- impl: CollapsedStatusBarViewModelImpl
- ): CollapsedStatusBarViewModel
+ abstract fun homeStatusBarViewModel(impl: HomeStatusBarViewModelImpl): HomeStatusBarViewModel
@Binds
- abstract fun collapsedStatusBarViewBinder(
- impl: CollapsedStatusBarViewBinderImpl
- ): CollapsedStatusBarViewBinder
+ abstract fun homeStatusBarViewBinder(impl: HomeStatusBarViewBinderImpl): HomeStatusBarViewBinder
companion object {
@@ -162,7 +158,7 @@ abstract class StatusBarPipelineModule {
@SysUISingleton
@Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
- mobileIconsViewModel: MobileIconsViewModel,
+ mobileIconsViewModel: MobileIconsViewModel
): Supplier<Flow<Boolean>> {
return Supplier<Flow<Boolean>> {
mobileIconsViewModel.firstMobileSubShowingNetworkTypeIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 3a07d9b6beaf..8d7b57db4125 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -33,31 +33,32 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.launch
/**
- * Interface to assist with binding the [CollapsedStatusBarFragment] to
- * [CollapsedStatusBarViewModel]. Used only to enable easy testing of [CollapsedStatusBarFragment].
+ * Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel].
+ * Used only to enable easy testing of [CollapsedStatusBarFragment].
*/
-interface CollapsedStatusBarViewBinder {
+interface HomeStatusBarViewBinder {
/**
* Binds the view to the view-model. [listener] will be notified whenever an event that may
* change the status bar visibility occurs.
*/
fun bind(
view: View,
- viewModel: CollapsedStatusBarViewModel,
+ viewModel: HomeStatusBarViewModel,
listener: StatusBarVisibilityChangeListener,
)
}
@SysUISingleton
-class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBarViewBinder {
+class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinder {
override fun bind(
view: View,
- viewModel: CollapsedStatusBarViewModel,
+ viewModel: HomeStatusBarViewModel,
listener: StatusBarVisibilityChangeListener,
) {
view.repeatWhenAttached {
@@ -185,9 +186,8 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
}
}
- private fun OngoingActivityChipModel.toVisibilityModel():
- CollapsedStatusBarViewModel.VisibilityModel {
- return CollapsedStatusBarViewModel.VisibilityModel(
+ private fun OngoingActivityChipModel.toVisibilityModel(): VisibilityModel {
+ return VisibilityModel(
visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
// TODO(b/364653005): Figure out the animation story here.
shouldAnimateChange = true,
@@ -224,7 +224,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
.start()
}
- private fun View.adjustVisibility(model: CollapsedStatusBarViewModel.VisibilityModel) {
+ private fun View.adjustVisibility(model: VisibilityModel) {
if (model.visibility == View.VISIBLE) {
this.show(model.shouldAnimateChange)
} else {
@@ -298,7 +298,7 @@ interface StatusBarVisibilityChangeListener {
/**
* Called when the scene state has changed such that the home status bar is newly allowed or no
- * longer allowed. See [CollapsedStatusBarViewModel.isHomeStatusBarAllowedByScene].
+ * longer allowed. See [HomeStatusBarViewModel.isHomeStatusBarAllowedByScene].
*/
fun onIsHomeStatusBarAllowedBySceneChanged(isHomeStatusBarAllowedByScene: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/RetroText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/RetroText.kt
new file mode 100644
index 000000000000..71bbdeaeb475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/RetroText.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.composable
+
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+private val retroColors =
+ listOf(
+ Color(0xFFEADFB4), // beige
+ Color(0xFF9BB0C1), // gray-blue
+ Color(0xFFF6995C), // orange
+ Color(0xFF51829B), // cyan
+ )
+
+/** Render a single string multiple times (with offsets) kinda like retro vintage text */
+@Composable
+fun RetroText(text: String = "") {
+ // Render the text for each retroColor, and then once for the foreground
+ for (i in retroColors.size downTo 1) {
+ val color = retroColors[i - 1]
+ RetroTextLayer(text = text, color = color, (-1.5 * i).dp, i.dp)
+ }
+
+ RetroTextLayer(text = text, color = Color.Black, ox = 0.dp, oy = 0.dp)
+}
+
+@Composable
+fun RetroTextLayer(text: String, color: Color, ox: Dp, oy: Dp) {
+ Text(
+ text = text,
+ modifier = Modifier.offset(ox, oy),
+ textAlign = TextAlign.Center,
+ style =
+ TextStyle(
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ fontStyle = FontStyle.Italic,
+ color = color,
+ ),
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
new file mode 100644
index 000000000000..440eb91841ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -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.systemui.statusbar.pipeline.shared.ui.composable
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.PhoneStatusBarView
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
+import com.android.systemui.statusbar.phone.ui.DarkIconManager
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.HomeStatusBarViewBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+
+/** Factory to simplify the dependency management for [StatusBarRoot] */
+class StatusBarRootFactory
+@Inject
+constructor(
+ private val context: Context,
+ private val homeStatusBarViewModel: HomeStatusBarViewModel,
+ private val homeStatusBarViewBinder: HomeStatusBarViewBinder,
+ private val notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
+ private val darkIconManagerFactory: DarkIconManager.Factory,
+ private val iconController: StatusBarIconController,
+ private val ongoingCallController: OngoingCallController,
+) {
+ fun create(root: ViewGroup, andThen: (ViewGroup) -> Unit): ComposeView {
+ val composeView = ComposeView(context)
+ composeView.apply {
+ setContent {
+ StatusBarRoot(
+ parent = root,
+ statusBarViewModel = homeStatusBarViewModel,
+ statusBarViewBinder = homeStatusBarViewBinder,
+ notificationIconsBinder = notificationIconsBinder,
+ darkIconManagerFactory = darkIconManagerFactory,
+ iconController = iconController,
+ ongoingCallController = ongoingCallController,
+ onViewCreated = andThen,
+ )
+ }
+ }
+
+ return composeView
+ }
+}
+
+/**
+ * For now, this class exists only to replace the former CollapsedStatusBarFragment. We simply stand
+ * up the PhoneStatusBarView here (allowing the component to be initialized from the [init] block).
+ * This is the place, for now, where we can manually set up lingering dependencies that came from
+ * the fragment until we can move them to recommended-arch style repos.
+ *
+ * @param onViewCreated called immediately after the view is inflated, and takes as a parameter the
+ * newly-inflated PhoneStatusBarView. This lambda is useful for tying together old initialization
+ * logic until it can be replaced.
+ */
+@Composable
+fun StatusBarRoot(
+ parent: ViewGroup,
+ statusBarViewModel: HomeStatusBarViewModel,
+ statusBarViewBinder: HomeStatusBarViewBinder,
+ notificationIconsBinder: NotificationIconContainerStatusBarViewBinder,
+ darkIconManagerFactory: DarkIconManager.Factory,
+ iconController: StatusBarIconController,
+ ongoingCallController: OngoingCallController,
+ onViewCreated: (ViewGroup) -> Unit,
+) {
+ // None of these methods are used when [StatusBarSimpleFragment] is on.
+ // This can be deleted once the fragment is gone
+ val nopVisibilityChangeListener =
+ object : StatusBarVisibilityChangeListener {
+ override fun onStatusBarVisibilityMaybeChanged() {}
+
+ override fun onTransitionFromLockscreenToDreamStarted() {}
+
+ override fun onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity: Boolean,
+ hasSecondaryOngoingActivity: Boolean,
+ shouldAnimate: Boolean,
+ ) {}
+
+ override fun onIsHomeStatusBarAllowedBySceneChanged(
+ isHomeStatusBarAllowedByScene: Boolean
+ ) {}
+ }
+
+ Box(Modifier.fillMaxSize()) {
+ // TODO(b/364360986): remove this before rolling the flag forward
+ Disambiguation(viewModel = statusBarViewModel)
+
+ Row(Modifier.fillMaxSize()) {
+ val scope = rememberCoroutineScope()
+ AndroidView(
+ factory = { context ->
+ val inflater = LayoutInflater.from(context)
+ val phoneStatusBarView =
+ inflater.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+
+ // For now, just set up the system icons the same way we used to
+ val statusIconContainer =
+ phoneStatusBarView.requireViewById<StatusIconContainer>(R.id.statusIcons)
+ // TODO(b/364360986): turn this into a repo/intr/viewmodel
+ val darkIconManager =
+ darkIconManagerFactory.create(statusIconContainer, StatusBarLocation.HOME)
+ iconController.addIconGroup(darkIconManager)
+
+ // TODO(b/372657935): This won't be needed once OngoingCallController is
+ // implemented in recommended architecture
+ ongoingCallController.setChipView(
+ phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
+ )
+
+ // For notifications, first inflate the [NotificationIconContainer]
+ val notificationIconArea =
+ phoneStatusBarView.requireViewById<ViewGroup>(R.id.notification_icon_area)
+ inflater.inflate(R.layout.notification_icon_area, notificationIconArea, true)
+ // Then bind it using the icons binder
+ val notificationIconContainer =
+ phoneStatusBarView.requireViewById<NotificationIconContainer>(
+ R.id.notificationIcons
+ )
+ scope.launch {
+ notificationIconsBinder.bindWhileAttached(notificationIconContainer)
+ }
+
+ // This binder handles everything else
+ scope.launch {
+ statusBarViewBinder.bind(
+ phoneStatusBarView,
+ statusBarViewModel,
+ nopVisibilityChangeListener,
+ )
+ }
+ onViewCreated(phoneStatusBarView)
+ phoneStatusBarView
+ }
+ )
+ }
+ }
+}
+
+/**
+ * This is our analog of the flexi "ribbon", which just shows some text so we know if the flag is on
+ */
+@Composable
+fun Disambiguation(viewModel: HomeStatusBarViewModel) {
+ val clockVisibilityModel =
+ viewModel.isClockVisible.collectAsStateWithLifecycle(
+ initialValue =
+ HomeStatusBarViewModel.VisibilityModel(
+ visibility = View.GONE,
+ shouldAnimateChange = false,
+ )
+ )
+ if (clockVisibilityModel.value.visibility == View.VISIBLE) {
+ Box(modifier = Modifier.fillMaxSize().alpha(0.5f), contentAlignment = Alignment.Center) {
+ RetroText(text = "COMPOSE->BAR")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 366ea3516965..4277a8be64d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -38,7 +39,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel.VisibilityModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -61,7 +62,7 @@ import kotlinx.coroutines.flow.stateIn
* [StatusBarHideIconsForBouncerManager]. We should move those pieces of logic to this class instead
* so that it's all in one place and easily testable outside of the fragment.
*/
-interface CollapsedStatusBarViewModel {
+interface HomeStatusBarViewModel {
/**
* True if the device is currently transitioning from lockscreen to occluded and false
* otherwise.
@@ -116,19 +117,20 @@ interface CollapsedStatusBarViewModel {
}
@SysUISingleton
-class CollapsedStatusBarViewModelImpl
+class HomeStatusBarViewModelImpl
@Inject
constructor(
collapsedStatusBarInteractor: CollapsedStatusBarInteractor,
private val lightsOutInteractor: LightsOutInteractor,
private val notificationsInteractor: ActiveNotificationsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
sceneInteractor: SceneInteractor,
sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
shadeInteractor: ShadeInteractor,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
@Application coroutineScope: CoroutineScope,
-) : CollapsedStatusBarViewModel {
+) : HomeStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
keyguardTransitionInteractor
.isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED))
@@ -184,29 +186,43 @@ constructor(
// TODO(b/364360986): Add edge cases, like secure camera launch.
}
- private val isHomeScreenStatusBarAllowed: Flow<Boolean> =
+ private val isHomeStatusBarAllowed: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
isHomeStatusBarAllowedByScene
} else {
isHomeScreenStatusBarAllowedLegacy
}
+ private val shouldHomeStatusBarBeVisible =
+ combine(isHomeStatusBarAllowed, keyguardInteractor.isSecureCameraActive) {
+ isHomeStatusBarAllowed,
+ isSecureCameraActive ->
+ // When launching the camera over the lockscreen, the status icons would typically
+ // become visible momentarily before animating out, since we're not yet aware that the
+ // launching camera activity is fullscreen. Even once the activity finishes launching,
+ // it takes a short time before WM decides that the top app wants to hide the icons and
+ // tells us to hide them.
+ // To ensure that this high-visibility animation is smooth, keep the icons hidden during
+ // a camera launch. See b/257292822.
+ isHomeStatusBarAllowed && !isSecureCameraActive
+ }
+
override val isClockVisible: Flow<VisibilityModel> =
combine(
- isHomeScreenStatusBarAllowed,
+ shouldHomeStatusBarBeVisible,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
- ) { isStatusBarAllowed, visibilityViaDisableFlags ->
- val showClock = isStatusBarAllowed && visibilityViaDisableFlags.isClockAllowed
+ ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
+ val showClock = shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed
// TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account.
VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate)
}
override val isNotificationIconContainerVisible: Flow<VisibilityModel> =
combine(
- isHomeScreenStatusBarAllowed,
+ shouldHomeStatusBarBeVisible,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
- ) { isStatusBarAllowed, visibilityViaDisableFlags ->
+ ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
val showNotificationIconContainer =
- isStatusBarAllowed && visibilityViaDisableFlags.areNotificationIconsAllowed
+ shouldStatusBarBeVisible && visibilityViaDisableFlags.areNotificationIconsAllowed
VisibilityModel(
showNotificationIconContainer.toVisibilityInt(),
visibilityViaDisableFlags.animate,
@@ -214,10 +230,11 @@ constructor(
}
override val isSystemInfoVisible: Flow<VisibilityModel> =
combine(
- isHomeScreenStatusBarAllowed,
+ shouldHomeStatusBarBeVisible,
collapsedStatusBarInteractor.visibilityViaDisableFlags,
- ) { isStatusBarAllowed, visibilityViaDisableFlags ->
- val showSystemInfo = isStatusBarAllowed && visibilityViaDisableFlags.isSystemInfoAllowed
+ ) { shouldStatusBarBeVisible, visibilityViaDisableFlags ->
+ val showSystemInfo =
+ shouldStatusBarBeVisible && visibilityViaDisableFlags.isSystemInfoAllowed
VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 7d0dadcf8c6e..7a88dcd92b88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -17,78 +17,40 @@
package com.android.systemui.statusbar.window
import android.content.Context
-import android.view.Display
import android.view.WindowManager
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
-import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Store that allows to retrieve per display instances of [StatusBarWindowController]. */
-interface StatusBarWindowControllerStore {
- /**
- * The instance for the default/main display of the device. For example, on a phone or a tablet,
- * the default display is the internal/built-in display of the device.
- *
- * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
- */
- val defaultDisplay: StatusBarWindowController
-
- /**
- * Returns an instance for a specific display id.
- *
- * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
- * displays.
- */
- fun forDisplay(displayId: Int): StatusBarWindowController
-}
+interface StatusBarWindowControllerStore : PerDisplayStore<StatusBarWindowController>
@SysUISingleton
class MultiDisplayStatusBarWindowControllerStore
@Inject
constructor(
- @Background private val backgroundApplicationScope: CoroutineScope,
+ @Background backgroundApplicationScope: CoroutineScope,
private val controllerFactory: StatusBarWindowController.Factory,
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
- private val displayRepository: DisplayRepository,
-) : StatusBarWindowControllerStore, CoreStartable {
+ displayRepository: DisplayRepository,
+) :
+ StatusBarWindowControllerStore,
+ PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) {
init {
StatusBarConnectedDisplays.assertInNewMode()
}
- private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>()
-
- override fun start() {
- backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) {
- displayRepository.displayRemovalEvent.collect { displayId ->
- perDisplayControllers.remove(displayId)
- }
- }
- }
-
- override val defaultDisplay: StatusBarWindowController
- get() = forDisplay(Display.DEFAULT_DISPLAY)
-
- override fun forDisplay(displayId: Int): StatusBarWindowController {
- if (displayRepository.getDisplay(displayId) == null) {
- throw IllegalArgumentException("Display with id $displayId doesn't exist.")
- }
- return perDisplayControllers.computeIfAbsent(displayId) {
- createControllerForDisplay(displayId)
- }
- }
-
- private fun createControllerForDisplay(displayId: Int): StatusBarWindowController {
+ override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController {
val statusBarDisplayContext =
displayWindowPropertiesRepository.get(
displayId = displayId,
@@ -101,6 +63,8 @@ constructor(
viewCaptureAwareWindowManager,
)
}
+
+ override val instanceClass = StatusBarWindowController::class.java
}
@SysUISingleton
@@ -110,16 +74,13 @@ constructor(
context: Context,
viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
factory: StatusBarWindowControllerImpl.Factory,
-) : StatusBarWindowControllerStore {
+) :
+ StatusBarWindowControllerStore,
+ PerDisplayStore<StatusBarWindowController> by SingleDisplayStore(
+ factory.create(context, viewCaptureAwareWindowManager)
+ ) {
init {
StatusBarConnectedDisplays.assertInLegacyMode()
}
-
- private val controller: StatusBarWindowController =
- factory.create(context, viewCaptureAwareWindowManager)
-
- override val defaultDisplay = controller
-
- override fun forDisplay(displayId: Int) = controller
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index d696979f1859..fbf54e7f5680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -30,8 +30,12 @@ import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
+import com.android.systemui.compose.ComposeInitializer;
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+
/**
* Status bar view.
+ * We now extend WindowRootView so that we can host Compose views
*/
public class StatusBarWindowView extends FrameLayout {
@@ -50,6 +54,24 @@ public class StatusBarWindowView extends FrameLayout {
}
@Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (StatusBarSimpleFragment.isEnabled()) {
+ ComposeInitializer.INSTANCE.onAttachedToWindow(this);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (StatusBarSimpleFragment.isEnabled()) {
+ ComposeInitializer.INSTANCE.onDetachedFromWindow(this);
+ }
+ }
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
mLeftInset = insets.left;
@@ -89,8 +111,8 @@ public class StatusBarWindowView extends FrameLayout {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- if (child.getLayoutParams() instanceof LayoutParams) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child.getLayoutParams() instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
if (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset
|| lp.topMargin != mTopInset) {
lp.rightMargin = mRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 75c66f234bdc..90c005139c56 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -67,7 +67,7 @@ class DistanceBasedGestureRecognizerProvider(
val distanceThresholdPx =
resources.getDimensionPixelSize(
com.android.internal.R.dimen.system_gestures_distance_threshold
- )
+ ) * 5
return remember(distanceThresholdPx) {
recognizerFactory(distanceThresholdPx, gestureStateChangedCallback)
}
@@ -77,7 +77,8 @@ class DistanceBasedGestureRecognizerProvider(
fun GestureState.toTutorialActionState(): TutorialActionState {
return when (this) {
NotStarted -> TutorialActionState.NotStarted
- is InProgress -> TutorialActionState.InProgress(progress)
+ // progress is disabled for now as views are not ready to handle varying progress
+ is InProgress -> TutorialActionState.InProgress(0f)
Finished -> TutorialActionState.Finished
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
index 56e97a357d67..80f800390852 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt
@@ -16,10 +16,14 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.util.MathUtils
import android.view.MotionEvent
import kotlin.math.abs
-/** Recognizes touchpad back gesture, that is three fingers swiping left or right */
+/**
+ * Recognizes touchpad back gesture, that is - using three fingers on touchpad - swiping left or
+ * right.
+ */
class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
private val distanceTracker = DistanceTracker()
@@ -36,7 +40,7 @@ class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu
gestureStateChangedCallback,
gestureState,
isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
- progress = { 0f },
+ progress = { MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
index 3db9d7ccc8f7..2b84a4c50613 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt
@@ -16,9 +16,10 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.util.MathUtils
import android.view.MotionEvent
-/** Recognizes touchpad home gesture, that is three fingers swiping up */
+/** Recognizes touchpad home gesture, that is - using three fingers on touchpad - swiping up. */
class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer {
private val distanceTracker = DistanceTracker()
@@ -35,7 +36,7 @@ class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu
gestureStateChangedCallback,
gestureState,
isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
- progress = { 0f },
+ progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
index a194ad6a8016..69b7c5edd750 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt
@@ -16,13 +16,14 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
+import android.util.MathUtils
import android.view.MotionEvent
import kotlin.math.abs
/**
- * Recognizes apps gesture completion. That is - using three fingers on touchpad - swipe up over
- * some distance threshold and then slow down gesture before fingers are lifted. Implementation is
- * based on [com.android.quickstep.util.TriggerSwipeUpTouchTracker]
+ * Recognizes recent apps gesture, that is - using three fingers on touchpad - swipe up over some
+ * distance threshold and then slow down gesture before fingers are lifted. Implementation is based
+ * on [com.android.quickstep.util.TriggerSwipeUpTouchTracker]
*/
class RecentAppsGestureRecognizer(
private val gestureDistanceThresholdPx: Int,
@@ -49,7 +50,7 @@ class RecentAppsGestureRecognizer(
-state.deltaY >= gestureDistanceThresholdPx &&
abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs
},
- progress = { 0f },
+ progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
index 3da725b9a51f..e590a7de09ad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -22,6 +22,7 @@ import android.provider.Settings
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.unit.dp
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -89,7 +90,7 @@ constructor(
VolumePanelRoute.SETTINGS_VOLUME_PANEL ->
activityStarter.startActivity(
/* intent= */ Intent(Settings.Panel.ACTION_VOLUME),
- /* dismissShade= */ true
+ /* dismissShade= */ true,
)
VolumePanelRoute.SYSTEM_UI_VOLUME_PANEL ->
volumePanelFactory.create(aboveStatusBar = true, view = null)
@@ -122,6 +123,9 @@ constructor(
remember(coroutineScope) { viewModelFactory.create(coroutineScope) }
)
},
+ isDraggable = false,
+ // TODO(b/337205027) change maxWidth
+ maxWidth = 800.dp,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 8b427fbc5fb8..071acfa44650 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -156,7 +156,7 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
createEndState(transitionContainer),
backgroundLayer,
fadeWindowBackgroundLayer,
- useSpring,
+ useSpring = useSpring,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index b87647ef9839..eb72f29046aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -48,11 +48,11 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.testing.AndroidTestingRunner;
-import android.testing.UiThreadTest;
+import androidx.test.annotation.UiThreadTest;
import android.view.Display;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -69,7 +69,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@UiThreadTest
public class DozeMachineTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index ae2a9add4fb7..6fce108f35a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -20,10 +20,10 @@ import android.animation.AnimatorTestRule
import android.content.Context
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
-import android.testing.UiThreadTest
import android.view.ContextThemeWrapper
import android.view.View
import android.widget.ImageView
+import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index 643debfbc810..7b24233d8603 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -17,13 +17,13 @@ import android.os.Handler;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.testing.TestableLooper;
-import android.testing.UiThreadTest;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
+import androidx.test.annotation.UiThreadTest;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt
deleted file mode 100644
index 0d1d37af7e5b..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.core
-
-import android.platform.test.annotations.EnableFlags
-import android.view.Display
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.display.data.repository.displayRepository
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.kosmos.unconfinedTestDispatcher
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-class MultiDisplayStatusBarInitializerStoreTest : SysuiTestCase() {
-
- private val kosmos =
- testKosmos().also {
- // Using unconfinedTestDispatcher to avoid having to call `runCurrent` in the tests.
- it.testDispatcher = it.unconfinedTestDispatcher
- }
- private val testScope = kosmos.testScope
- private val fakeDisplayRepository = kosmos.displayRepository
- private val store = kosmos.multiDisplayStatusBarInitializerStore
-
- @Before
- fun start() {
- store.start()
- }
-
- @Before
- fun addDisplays() = runBlocking {
- fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID)
- fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
- }
-
- @Test
- fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
- testScope.runTest {
- val controller = store.defaultDisplay
-
- assertThat(store.defaultDisplay).isSameInstanceAs(controller)
- }
-
- @Test
- fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
- testScope.runTest {
- val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
-
- assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
- }
-
- @Test
- fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
- testScope.runTest {
- val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
-
- fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
- fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
-
- assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun forDisplay_nonExistingDisplayId_throws() =
- testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }
-
- companion object {
- private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
- private const val NON_DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1
- private const val NON_EXISTING_DISPLAY_ID = Display.DEFAULT_DISPLAY + 2
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index 2d8e69280d30..c8ef663a4f81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -31,12 +31,12 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.testing.AndroidTestingRunner;
-import android.testing.UiThreadTest;
import android.util.FloatProperty;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
import com.android.app.animation.Interpolators;
@@ -270,4 +270,4 @@ public class PropertyAnimatorTest extends SysuiTestCase {
PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index bb6d0982173a..9b3a43dc0310 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -192,6 +192,8 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent;
+import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -538,8 +540,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
new StatusBarInitializerImpl(
mStatusBarWindowController,
mCollapsedStatusBarFragmentProvider,
- emptySet()
- ),
+ mock(StatusBarRootFactory.class),
+ mock(HomeStatusBarComponent.Factory.class),
+ emptySet()),
mStatusBarWindowControllerStore,
mStatusBarWindowStateController,
new FakeStatusBarModeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 15ef917343ee..d01c1ca36c4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -66,12 +66,12 @@ import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ui.DarkIconManager;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewBinder;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeHomeStatusBarViewModel;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -109,9 +109,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private final CarrierConfigTracker mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@Mock
- private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
+ private HomeStatusBarComponent.Factory mStatusBarFragmentComponentFactory;
@Mock
- private StatusBarFragmentComponent mStatusBarFragmentComponent;
+ private HomeStatusBarComponent mHomeStatusBarComponent;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -122,8 +122,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private DarkIconManager.Factory mIconManagerFactory;
@Mock
private DarkIconManager mIconManager;
- private FakeCollapsedStatusBarViewModel mCollapsedStatusBarViewModel;
- private FakeCollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder;
+ private FakeHomeStatusBarViewModel mCollapsedStatusBarViewModel;
+ private FakeHomeStatusBarViewBinder mCollapsedStatusBarViewBinder;
@Mock
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock
@@ -1060,7 +1060,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
public void setUp_fragmentCreatesDaggerComponent() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- assertEquals(mStatusBarFragmentComponent, fragment.getStatusBarFragmentComponent());
+ assertEquals(mHomeStatusBarComponent, fragment.getHomeStatusBarComponent());
}
@Test
@@ -1190,8 +1190,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mSecureSettings = mock(SecureSettings.class);
mShadeExpansionStateManager = new ShadeExpansionStateManager();
- mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel();
- mCollapsedStatusBarViewBinder = new FakeCollapsedStatusBarViewBinder();
+ mCollapsedStatusBarViewModel = new FakeHomeStatusBarViewModel();
+ mCollapsedStatusBarViewBinder = new FakeHomeStatusBarViewBinder();
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
@@ -1224,8 +1224,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private void setUpDaggerComponent() {
when(mStatusBarFragmentComponentFactory.create(any()))
- .thenReturn(mStatusBarFragmentComponent);
- when(mStatusBarFragmentComponent.getHeadsUpAppearanceController())
+ .thenReturn(mHomeStatusBarComponent);
+ when(mHomeStatusBarComponent.getHeadsUpAppearanceController())
.thenReturn(mHeadsUpAppearanceController);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
index 3087d01a2479..77ec83871016 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.internal.logging.metricsLogger
import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.data.repository.emergencyServicesRepository
+import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
@@ -52,5 +53,6 @@ val Kosmos.bouncerActionButtonInteractor by Fixture {
metricsLogger = metricsLogger,
dozeLogger = mock(),
sceneInteractor = { sceneInteractor },
+ bouncerHapticPlayer,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
new file mode 100644
index 000000000000..e3797260ed6d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.CoroutineScope
+
+class FakePerDisplayStore(
+ backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+) : PerDisplayStoreImpl<TestPerDisplayInstance>(backgroundApplicationScope, displayRepository) {
+
+ val removalActions = mutableListOf<TestPerDisplayInstance>()
+
+ override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance {
+ return TestPerDisplayInstance(displayId)
+ }
+
+ override val instanceClass = TestPerDisplayInstance::class.java
+
+ override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) {
+ removalActions += instance
+ }
+}
+
+data class TestPerDisplayInstance(val displayId: Int)
+
+val Kosmos.fakePerDisplayStore by
+ Kosmos.Fixture {
+ FakePerDisplayStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt
index 0ca025f53df4..742b79cc17d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSColumnsRepositoryKosmos.kt
@@ -19,9 +19,6 @@ 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.qsColumnsRepository by
- Kosmos.Fixture {
- QSColumnsRepository(applicationCoroutineScope, mainResources, configurationRepository)
- }
+var Kosmos.qsColumnsRepository by
+ Kosmos.Fixture { QSColumnsRepository(mainResources, configurationRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
index 02ed2648107b..47615f527d16 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSColumnsInteractorKosmos.kt
@@ -17,6 +17,11 @@
package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.data.repository.qsColumnsRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
-val Kosmos.qsColumnsInteractor by Kosmos.Fixture { QSColumnsInteractor(qsColumnsRepository) }
+val Kosmos.qsColumnsInteractor by
+ Kosmos.Fixture {
+ QSColumnsInteractor(applicationCoroutineScope, qsColumnsRepository, shadeInteractor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 8066b9138c99..303529b7f7b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -34,8 +34,8 @@ val Kosmos.multiDisplayStatusBarInitializerStore by
Kosmos.Fixture {
MultiDisplayStatusBarInitializerStore(
applicationCoroutineScope,
- fakeStatusBarInitializerFactory,
displayRepository,
+ fakeStatusBarInitializerFactory,
fakeStatusBarWindowControllerStore,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index 1c7fd4817498..3a7ada2e61b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -27,13 +28,14 @@ import com.android.systemui.statusbar.notification.domain.interactor.activeNotif
import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor
-val Kosmos.collapsedStatusBarViewModel: CollapsedStatusBarViewModel by
+val Kosmos.homeStatusBarViewModel: HomeStatusBarViewModel by
Kosmos.Fixture {
- CollapsedStatusBarViewModelImpl(
+ HomeStatusBarViewModelImpl(
collapsedStatusBarInteractor,
lightsOutInteractor,
activeNotificationsInteractor,
keyguardTransitionInteractor,
+ keyguardInteractor,
sceneInteractor,
sceneContainerOcclusionInteractor,
shadeInteractor,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 24950e65c174..afa7a6c51abb 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -225,14 +225,9 @@ public class RavenwoodRuntimeEnvironmentController {
ActivityManager.init$ravenwood(config.mCurrentUser);
- final HandlerThread main;
- if (config.mProvideMainThread) {
- main = new HandlerThread(MAIN_THREAD_NAME);
- main.start();
- Looper.setMainLooperForTest(main.getLooper());
- } else {
- main = null;
- }
+ final var main = new HandlerThread(MAIN_THREAD_NAME);
+ main.start();
+ Looper.setMainLooperForTest(main.getLooper());
final boolean isSelfInstrumenting =
Objects.equals(config.mTestPackageName, config.mTargetPackageName);
@@ -324,10 +319,8 @@ public class RavenwoodRuntimeEnvironmentController {
}
sMockUiAutomation.dropShellPermissionIdentity();
- if (config.mProvideMainThread) {
- Looper.getMainLooper().quit();
- Looper.clearMainLooperForTest();
- }
+ Looper.getMainLooper().quit();
+ Looper.clearMainLooperForTest();
ActivityManager.reset$ravenwood();
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 446f819ad41b..1f6e11dd5cf2 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -152,7 +152,10 @@ public final class RavenwoodConfig {
/**
* Configure a "main" thread to be available for the duration of the test, as defined
* by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
+ *
+ * @deprecated
*/
+ @Deprecated
public Builder setProvideMainThread(boolean provideMainThread) {
mConfig.mProvideMainThread = provideMainThread;
return this;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 4196d8e22610..93a6806ed1f4 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -139,7 +139,10 @@ public final class RavenwoodRule implements TestRule {
/**
* Configure a "main" thread to be available for the duration of the test, as defined
* by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
+ *
+ * @deprecated
*/
+ @Deprecated
public Builder setProvideMainThread(boolean provideMainThread) {
mBuilder.setProvideMainThread(provideMainThread);
return this;
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index ac499b966afe..d7f4b3e2955d 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -54,34 +54,36 @@ android_ravenwood_test {
auto_gen_config: true,
}
-// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
+android_test {
+ name: "RavenwoodBivalentTest_device",
-// android_test {
-// name: "RavenwoodBivalentTest_device",
-//
-// srcs: [
-// "test/**/*.java",
-// ],
-// static_libs: [
-// "junit",
-// "truth",
-//
-// "androidx.annotation_annotation",
-// "androidx.test.ext.junit",
-// "androidx.test.rules",
-//
-// "junit-params",
-// "platform-parametric-runner-lib",
-//
-// "ravenwood-junit",
-// ],
-// jni_libs: [
-// "libravenwoodbivalenttest_jni",
-// ],
-// test_suites: [
-// "device-tests",
-// ],
-// optimize: {
-// enabled: false,
-// },
-// }
+ srcs: [
+ "test/**/*.java",
+ ],
+ // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
+ exclude_srcs: [
+ "test/**/ravenizer/*.java",
+ ],
+ static_libs: [
+ "junit",
+ "truth",
+
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+
+ "junit-params",
+ "platform-parametric-runner-lib",
+
+ "ravenwood-junit",
+ ],
+ jni_libs: [
+ "libravenwoodbivalenttest_jni",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/Android.bp b/services/Android.bp
index 653cd3c3b680..f04c692c12d0 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -195,7 +195,17 @@ soong_config_module_type {
module_type: "java_library",
config_namespace: "system_services",
bool_variables: ["without_vibrator"],
- properties: ["vintf_fragments"],
+ properties: ["vintf_fragment_modules"],
+}
+
+vintf_fragment {
+ name: "manifest_services.xml",
+ src: "manifest_services.xml",
+}
+
+vintf_fragment {
+ name: "manifest_services_android.frameworks.vibrator.xml",
+ src: "manifest_services_android.frameworks.vibrator.xml",
}
system_java_library {
@@ -264,11 +274,11 @@ system_java_library {
soong_config_variables: {
without_vibrator: {
- vintf_fragments: [
+ vintf_fragment_modules: [
"manifest_services.xml",
],
conditions_default: {
- vintf_fragments: [
+ vintf_fragment_modules: [
"manifest_services.xml",
"manifest_services_android.frameworks.vibrator.xml",
],
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 034127c0420e..7057cc361a1a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,16 @@ flag {
}
flag {
+ name: "clear_shortcuts_when_activity_updates_to_service"
+ namespace: "accessibility"
+ description: "When an a11y activity is updated to an a11y service, clears the associated shortcuts so that we don't skip the AccessibilityServiceWarning."
+ bug: "358092445"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "compute_window_changes_on_a11y_v2"
namespace: "accessibility"
description: "Computes accessibility window changes in accessibility instead of wm package."
@@ -114,10 +124,13 @@ flag {
}
flag {
- name: "enable_magnification_follows_mouse"
+ name: "enable_magnification_follows_mouse_bugfix"
namespace: "accessibility"
description: "Whether to enable mouse following for fullscreen magnification"
bug: "354696546"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1451dfaa7964..ec8908bc7c91 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2513,6 +2513,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
+ if (Flags.clearShortcutsWhenActivityUpdatesToService()) {
+ List<String> componentNames = userState.mInstalledShortcuts.stream()
+ .filter(a11yActivity ->
+ !parsedAccessibilityShortcutInfos.contains(a11yActivity))
+ .map(a11yActivity -> a11yActivity.getComponentName().flattenToString())
+ .toList();
+ if (!componentNames.isEmpty()) {
+ enableShortcutsForTargets(
+ /* enable= */ false, UserShortcutType.ALL,
+ componentNames, userState.mUserId);
+ }
+ }
+
userState.mInstalledShortcuts.clear();
userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
userState.updateTileServiceMapForAccessibilityActivityLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index a19fdddea49c..963334b07ea6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -345,7 +345,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@Override
void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (Flags.enableMagnificationFollowsMouse()) {
+ if (Flags.enableMagnificationFollowsMouseBugfix()) {
if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
// TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
// over, rather than only interacting with the current display.
@@ -1206,7 +1206,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
- if (Flags.enableMagnificationFollowsMouse()
+ if (Flags.enableMagnificationFollowsMouseBugfix()
&& !event.isFromSource(SOURCE_TOUCHSCREEN)) {
// Only touch events need to be cached and sent later.
return;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 446123f07f64..fa86ba39bb1a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -146,7 +146,8 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo
} break;
case SOURCE_MOUSE:
case SOURCE_STYLUS: {
- if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) {
+ if (magnificationShortcutExists()
+ && Flags.enableMagnificationFollowsMouseBugfix()) {
handleMouseOrStylusEvent(event, rawEvent, policyFlags);
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index c3b7087a44c3..44ae1d1fbbbf 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -31,7 +31,8 @@ public final class AppFunctionExecutors {
/* maxConcurrency= */ Runtime.getRuntime().availableProcessors(),
/* keepAliveTime= */ 0L,
/* unit= */ TimeUnit.SECONDS,
- /* workQueue= */ new LinkedBlockingQueue<>());
+ /* workQueue= */ new LinkedBlockingQueue<>(),
+ new NamedThreadFactory("AppFunctionExecutors"));
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 28e57775523b..89f14b09d397 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -68,7 +68,10 @@ import com.android.server.SystemService.TargetUser;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
+import java.util.WeakHashMap;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
@@ -81,7 +84,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
private final Context mContext;
- private final Object mLock = new Object();
+ private final Map<String, Object> mLocks = new WeakHashMap<>();
+
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
@@ -316,9 +320,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
THREAD_POOL_EXECUTOR.execute(
() -> {
try {
- // TODO(357551503): Instead of holding a global lock, hold a per-package
- // lock.
- synchronized (mLock) {
+ synchronized (getLockForPackage(callingPackage)) {
setAppFunctionEnabledInternalLocked(
callingPackage, functionIdentifier, userHandle, enabledState);
}
@@ -346,7 +348,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
* process.
*/
@WorkerThread
- @GuardedBy("mLock")
+ @GuardedBy("getLockForPackage(callingPackage)")
private void setAppFunctionEnabledInternalLocked(
@NonNull String callingPackage,
@NonNull String functionIdentifier,
@@ -541,6 +543,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
});
}
}
+ /**
+ * Retrieves the lock object associated with the given package name.
+ *
+ * This method returns the lock object from the {@code mLocks} map if it exists.
+ * If no lock is found for the given package name, a new lock object is created,
+ * stored in the map, and returned.
+ */
+ @VisibleForTesting
+ @NonNull
+ Object getLockForPackage(String callingPackage) {
+ // Synchronized the access to mLocks to prevent race condition.
+ synchronized (mLocks) {
+ // By using a WeakHashMap, we allow the garbage collector to reclaim memory by removing
+ // entries associated with unused callingPackage keys. Therefore, we remove the null
+ // values before getting/computing a new value. The goal is to not let the size of this
+ // map grow without an upper bound.
+ mLocks.values().removeAll(Collections.singleton(null)); // Remove null values
+ return mLocks.computeIfAbsent(callingPackage, k -> new Object());
+ }
+ }
private static class AppFunctionMetadataObserver implements ObserverCallback {
@Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 96be76975e9d..cc73288cdbfa 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -84,7 +84,9 @@ public class MetadataSyncAdapter {
@NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
- mExecutor = Executors.newSingleThreadExecutor();
+ mExecutor =
+ Executors.newSingleThreadExecutor(
+ new NamedThreadFactory("AppFunctionSyncExecutors"));
}
/**
diff --git a/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java b/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java
new file mode 100644
index 000000000000..7adcc48a6a35
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/NamedThreadFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** A {@link ThreadFactory} that creates threads with a given base name. */
+public class NamedThreadFactory implements ThreadFactory {
+ private final ThreadFactory mDefaultThreadFactory;
+ private final String mBaseName;
+ private final AtomicInteger mCount = new AtomicInteger(0);
+
+ public NamedThreadFactory(final String baseName) {
+ mDefaultThreadFactory = Executors.defaultThreadFactory();
+ mBaseName = baseName;
+ }
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ final Thread thread = mDefaultThreadFactory.newThread(runnable);
+ thread.setName(mBaseName + "-" + mCount.getAndIncrement());
+ return thread;
+ }
+}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 504137a29977..6a1e319b4039 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@ import android.util.TimingsTraceLog;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.build.UnboundedSdkLevel;
import com.android.server.pm.permission.PermissionAllowlist;
@@ -212,6 +213,30 @@ public class SystemConfig {
}
}
+ /**
+ * Utility class for testing interaction with compile-time defined system features.
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ /** Whether a system feature is defined as enabled and available at compile-time. */
+ public boolean isReadOnlySystemEnabledFeature(String featureName, int version) {
+ return Boolean.TRUE.equals(RoSystemFeatures.maybeHasFeature(featureName, version));
+ }
+
+ /** Whether a system feature is defined as disabled and unavailable at compile-time. */
+ public boolean isReadOnlySystemDisabledFeature(String featureName, int version) {
+ return Boolean.FALSE.equals(RoSystemFeatures.maybeHasFeature(featureName, version));
+ }
+
+ /** The full set of system features defined as compile-time enabled and available. */
+ public ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
+ return RoSystemFeatures.getReadOnlySystemEnabledFeatures();
+ }
+ }
+
+ private final Injector mInjector;
+
// These are the built-in shared libraries that were read from the
// system configuration files. Keys are the library names; values are
// the individual entries that contain information such as filename
@@ -220,7 +245,7 @@ public class SystemConfig {
// These are the features this devices supports that were read from the
// system configuration files.
- final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
+ final ArrayMap<String, FeatureInfo> mAvailableFeatures;
// These are the features which this device doesn't support; the OEM
// partition uses these to opt-out of features from the system image.
@@ -602,12 +627,26 @@ public class SystemConfig {
public ArrayMap<String, Integer> getOemDefinedUids() {
return mOemDefinedUids;
}
+
/**
* Only use for testing. Do NOT use in production code.
* @param readPermissions false to create an empty SystemConfig; true to read the permissions.
*/
@VisibleForTesting
public SystemConfig(boolean readPermissions) {
+ this(readPermissions, new Injector());
+ }
+
+ /**
+ * Only use for testing. Do NOT use in production code.
+ * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
+ * @param injector Additional dependency injection for testing.
+ */
+ @VisibleForTesting
+ public SystemConfig(boolean readPermissions, Injector injector) {
+ mInjector = injector;
+ mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures();
+
if (readPermissions) {
Slog.w(TAG, "Constructing a test SystemConfig");
readAllPermissions();
@@ -617,6 +656,9 @@ public class SystemConfig {
}
SystemConfig() {
+ mInjector = new Injector();
+ mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures();
+
TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
log.traceBegin("readAllPermissions");
try {
@@ -1775,6 +1817,10 @@ public class SystemConfig {
}
private void addFeature(String name, int version) {
+ if (mInjector.isReadOnlySystemDisabledFeature(name, version)) {
+ Slog.w(TAG, "Skipping feature addition for compile-time disabled feature: " + name);
+ return;
+ }
FeatureInfo fi = mAvailableFeatures.get(name);
if (fi == null) {
fi = new FeatureInfo();
@@ -1787,6 +1833,10 @@ public class SystemConfig {
}
private void removeFeature(String name) {
+ if (mInjector.isReadOnlySystemEnabledFeature(name, /*version=*/0)) {
+ Slog.w(TAG, "Skipping feature removal for compile-time enabled feature: " + name);
+ return;
+ }
if (mAvailableFeatures.remove(name) != null) {
Slog.d(TAG, "Removed unavailable feature " + name);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f1d912d9a79..746c55f8fc9d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5542,7 +5542,6 @@ public class ActivityManagerService extends IActivityManager.Stub
public int sendIntentSender(IApplicationThread caller, IIntentSender target,
IBinder allowlistToken, int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- addCreatorToken(intent);
if (target instanceof PendingIntentRecord) {
final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
@@ -5584,19 +5583,23 @@ public class ActivityManagerService extends IActivityManager.Stub
intent = new Intent(Intent.ACTION_MAIN);
}
try {
+ final int callingUid = Binder.getCallingUid();
+ final String packageName;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
if (allowlistToken != null) {
- final int callingUid = Binder.getCallingUid();
- final String packageName;
- final long token = Binder.clearCallingIdentity();
- try {
- packageName = AppGlobals.getPackageManager().getNameForUid(callingUid);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target."
+ " Calling package: " + packageName + "; intent: " + intent
+ "; options: " + options);
}
+
+ addCreatorToken(intent, packageName);
+
target.send(code, intent, resolvedType, null, null,
requiredPermission, options);
} catch (RemoteException e) {
@@ -12371,7 +12374,7 @@ public class ActivityManagerService extends IActivityManager.Stub
continue;
}
endTime = SystemClock.currentThreadTimeMillis();
- hasSwapPss = mi.hasSwappedOutPss;
+ hasSwapPss = hasSwapPss || mi.hasSwappedOutPss;
memtrackGraphics = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS);
memtrackGl = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GL);
} else {
@@ -13049,7 +13052,7 @@ public class ActivityManagerService extends IActivityManager.Stub
continue;
}
endTime = SystemClock.currentThreadTimeMillis();
- hasSwapPss = mi.hasSwappedOutPss;
+ hasSwapPss = hasSwapPss || mi.hasSwappedOutPss;
} else {
reportType = ProcessStats.ADD_PSS_EXTERNAL;
startTime = SystemClock.currentThreadTimeMillis();
@@ -13628,7 +13631,7 @@ public class ActivityManagerService extends IActivityManager.Stub
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
- addCreatorToken(service);
+ addCreatorToken(service, callingPackage);
if (service != null) {
// Refuse possible leaked file descriptors
if (service.hasFileDescriptors()) {
@@ -13890,7 +13893,7 @@ public class ActivityManagerService extends IActivityManager.Stub
validateServiceInstanceName(instanceName);
- addCreatorToken(service);
+ addCreatorToken(service, callingPackage);
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
final ComponentName cn = service.getComponent();
@@ -17174,7 +17177,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.v(TAG_SERVICE,
"startServiceInPackage: " + service + " type=" + resolvedType);
}
- addCreatorToken(service);
+ addCreatorToken(service, callingPackage);
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
@@ -18002,8 +18005,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void addCreatorToken(Intent intent) {
- ActivityManagerService.this.addCreatorToken(intent);
+ public void addCreatorToken(Intent intent, String creatorPackage) {
+ ActivityManagerService.this.addCreatorToken(intent, creatorPackage);
}
}
@@ -19160,9 +19163,9 @@ public class ActivityManagerService extends IActivityManager.Stub
private final Key mKeyFields;
private final WeakReference<IntentCreatorToken> mRef;
- public IntentCreatorToken(int creatorUid, Intent intent) {
+ public IntentCreatorToken(int creatorUid, String creatorPackage, Intent intent) {
super();
- this.mKeyFields = new Key(creatorUid, intent);
+ this.mKeyFields = new Key(creatorUid, creatorPackage, intent);
mRef = new WeakReference<>(this);
}
@@ -19170,7 +19173,10 @@ public class ActivityManagerService extends IActivityManager.Stub
return mKeyFields.mCreatorUid;
}
- /** {@hide} */
+ public String getCreatorPackage() {
+ return mKeyFields.mCreatorPackage;
+ }
+
public static boolean isValid(@NonNull Intent intent) {
IBinder binder = intent.getCreatorToken();
IntentCreatorToken token = null;
@@ -19178,7 +19184,8 @@ public class ActivityManagerService extends IActivityManager.Stub
token = (IntentCreatorToken) binder;
}
return token != null && token.mKeyFields.equals(
- new Key(token.mKeyFields.mCreatorUid, intent));
+ new Key(token.mKeyFields.mCreatorUid, token.mKeyFields.mCreatorPackage,
+ intent));
}
@Override
@@ -19202,8 +19209,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private static class Key {
- private Key(int creatorUid, Intent intent) {
+ private Key(int creatorUid, String creatorPackage, Intent intent) {
this.mCreatorUid = creatorUid;
+ this.mCreatorPackage = creatorPackage;
this.mAction = intent.getAction();
this.mData = intent.getData();
this.mType = intent.getType();
@@ -19220,6 +19228,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private final int mCreatorUid;
+ private final String mCreatorPackage;
private final String mAction;
private final Uri mData;
private final String mType;
@@ -19233,17 +19242,20 @@ public class ActivityManagerService extends IActivityManager.Stub
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
- return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags && Objects.equals(
- mAction, key.mAction) && Objects.equals(mData, key.mData)
- && Objects.equals(mType, key.mType) && Objects.equals(mPackage,
- key.mPackage) && Objects.equals(mComponent, key.mComponent)
+ return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags
+ && Objects.equals(mCreatorPackage, key.mCreatorPackage)
+ && Objects.equals(mAction, key.mAction)
+ && Objects.equals(mData, key.mData)
+ && Objects.equals(mType, key.mType)
+ && Objects.equals(mPackage, key.mPackage)
+ && Objects.equals(mComponent, key.mComponent)
&& Objects.equals(mClipDataUris, key.mClipDataUris);
}
@Override
public int hashCode() {
- return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
- mFlags, mClipDataUris);
+ return Objects.hash(mCreatorUid, mCreatorPackage, mAction, mData, mType, mPackage,
+ mComponent, mFlags, mClipDataUris);
}
}
}
@@ -19254,7 +19266,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* @param intent The given intent
* @hide
*/
- public void addCreatorToken(@Nullable Intent intent) {
+ public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
if (!preventIntentRedirect()) return;
if (intent == null || intent.getExtraIntentKeys() == null) return;
@@ -19267,7 +19279,7 @@ public class ActivityManagerService extends IActivityManager.Stub
continue;
}
Slog.wtf(TAG, "A creator token is added to an intent.");
- IBinder creatorToken = createIntentCreatorToken(extraIntent);
+ IBinder creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
if (creatorToken != null) {
extraIntent.setCreatorToken(creatorToken);
}
@@ -19280,15 +19292,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private IBinder createIntentCreatorToken(Intent intent) {
+ private IBinder createIntentCreatorToken(Intent intent, String creatorPackage) {
if (IntentCreatorToken.isValid(intent)) return null;
int creatorUid = getCallingUid();
- IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, intent);
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
IntentCreatorToken token;
synchronized (sIntentCreatorTokenCache) {
WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
if (ref == null || ref.get() == null) {
- token = new IntentCreatorToken(creatorUid, intent);
+ token = new IntentCreatorToken(creatorUid, creatorPackage, intent);
sIntentCreatorTokenCache.put(key, token.mRef);
} else {
token = ref.get();
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 15f1085b7125..a00cac6aba4f 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -258,6 +258,7 @@ class BroadcastController {
final StringBuilder sb = new StringBuilder("registerReceiver: ");
sb.append(Binder.getCallingUid()); sb.append('/');
sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+ sb.append("p:"); sb.append(filter.getPriority()); sb.append('/');
final int actionsCount = filter.safeCountActions();
if (actionsCount > 0) {
for (int i = 0; i < actionsCount; ++i) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 776a3455acc4..f60ee66cb236 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3283,7 +3283,12 @@ public class OomAdjuster {
baseCapabilities = PROCESS_CAPABILITY_ALL; // BFSL allowed
break;
case PROCESS_STATE_BOUND_TOP:
- baseCapabilities = PROCESS_CAPABILITY_BFSL;
+ if (app.getActiveInstrumentation() != null) {
+ baseCapabilities = PROCESS_CAPABILITY_BFSL |
+ PROCESS_CAPABILITY_ALL_IMPLICIT;
+ } else {
+ baseCapabilities = PROCESS_CAPABILITY_BFSL;
+ }
break;
case PROCESS_STATE_FOREGROUND_SERVICE:
if (app.getActiveInstrumentation() != null) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 6857b6bcde15..3fb06a75d79f 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -432,6 +432,14 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
}
}
+ /**
+ * get package name of the PendingIntent sender.
+ * @return package name of the PendingIntent sender.
+ */
+ public String getPackageName() {
+ return key.packageName;
+ }
+
@Deprecated
public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,
IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7831c393844b..cdb01889c139 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5187,6 +5187,7 @@ public final class ProcessList {
if (ai != null) {
if (ai.packageName.equals(app.info.packageName)) {
app.info = ai;
+ app.getWindowProcessController().updateApplicationInfo(ai);
PlatformCompatCache.getInstance()
.onApplicationInfoChanged(ai);
}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 636854b85ee4..d1576c5cca4f 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -17,118 +17,63 @@
package com.android.server.integrity;
import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
-import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
-import static android.content.Intent.EXTRA_ORIGINATING_UID;
-import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
-import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.integrity.IntegrityUtils.getHexDigest;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import android.annotation.BinderThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.integrity.AppInstallMetadata;
import android.content.integrity.IAppIntegrityManager;
import android.content.integrity.Rule;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
-import android.content.pm.Signature;
-import android.content.pm.SigningDetails;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.Settings;
import android.util.Pair;
import android.util.Slog;
-import android.util.apk.SourceStampVerificationResult;
-import android.util.apk.SourceStampVerifier;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.parsing.PackageParser2;
-import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
-import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.RuleMetadata;
-import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageParserUtils;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/** Implementation of {@link AppIntegrityManagerService}. */
public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
- /**
- * This string will be used as the "installer" for formula evaluation when the app's installer
- * cannot be determined.
- *
- * <p>This may happen for various reasons. e.g., the installing app's package name may not match
- * its UID.
- */
- private static final String UNKNOWN_INSTALLER = "";
- /**
- * This string will be used as the "installer" for formula evaluation when the app is being
- * installed via ADB.
- */
- public static final String ADB_INSTALLER = "adb";
private static final String TAG = "AppIntegrityManagerServiceImpl";
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
- private static final String BASE_APK_FILE = "base.apk";
- private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers";
- private static final String ALLOWED_INSTALLER_DELIMITER = ",";
- private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
public static final boolean DEBUG_INTEGRITY_COMPONENT = false;
- private static final Set<String> PACKAGE_INSTALLER =
- new HashSet<>(
- Arrays.asList(
- "com.google.android.packageinstaller", "com.android.packageinstaller"));
-
// Access to files inside mRulesDir is protected by mRulesLock;
private final Context mContext;
private final Handler mHandler;
private final PackageManagerInternal mPackageManagerInternal;
- private final Supplier<PackageParser2> mParserSupplier;
private final IntegrityFileManager mIntegrityFileManager;
/** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
@@ -139,7 +84,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
return new AppIntegrityManagerServiceImpl(
context,
LocalServices.getService(PackageManagerInternal.class),
- PackageParserUtils::forParsingFileWithDefaults,
IntegrityFileManager.getInstance(),
handlerThread.getThreadHandler());
}
@@ -148,12 +92,10 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
AppIntegrityManagerServiceImpl(
Context context,
PackageManagerInternal packageManagerInternal,
- Supplier<PackageParser2> parserSupplier,
IntegrityFileManager integrityFileManager,
Handler handler) {
mContext = context;
mPackageManagerInternal = packageManagerInternal;
- mParserSupplier = parserSupplier;
mIntegrityFileManager = integrityFileManager;
mHandler = handler;
@@ -263,148 +205,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
private void handleIntegrityVerification(Intent intent) {
int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
-
- try {
- if (DEBUG_INTEGRITY_COMPONENT) {
- Slog.d(TAG, "Received integrity verification intent " + intent.toString());
- Slog.d(TAG, "Extras " + intent.getExtras());
- }
-
- String installerPackageName = getInstallerPackageName(intent);
-
- // Skip integrity verification if the verifier is doing the install.
- if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) {
- if (DEBUG_INTEGRITY_COMPONENT) {
- Slog.i(TAG, "Verifier doing the install. Skipping integrity check.");
- }
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- return;
- }
-
- String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
-
- Pair<SigningDetails, Bundle> packageSigningAndMetadata =
- getPackageSigningAndMetadata(intent.getData());
- if (packageSigningAndMetadata == null) {
- Slog.w(TAG, "Cannot parse package " + packageName);
- // We can't parse the package.
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- return;
- }
-
- var signingDetails = packageSigningAndMetadata.first;
- List<String> appCertificates = getCertificateFingerprint(packageName, signingDetails);
- List<String> appCertificateLineage = getCertificateLineage(packageName, signingDetails);
- List<String> installerCertificates =
- getInstallerCertificateFingerprint(installerPackageName);
-
- AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
-
- builder.setPackageName(getPackageNameNormalized(packageName));
- builder.setAppCertificates(appCertificates);
- builder.setAppCertificateLineage(appCertificateLineage);
- builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
- builder.setInstallerName(getPackageNameNormalized(installerPackageName));
- builder.setInstallerCertificates(installerCertificates);
- builder.setIsPreInstalled(isSystemApp(packageName));
-
- Map<String, String> allowedInstallers =
- getAllowedInstallers(packageSigningAndMetadata.second);
- builder.setAllowedInstallersAndCert(allowedInstallers);
- extractSourceStamp(intent.getData(), builder);
-
- AppInstallMetadata appInstallMetadata = builder.build();
-
- if (DEBUG_INTEGRITY_COMPONENT) {
- Slog.i(
- TAG,
- "To be verified: "
- + appInstallMetadata
- + " installers "
- + allowedInstallers);
- }
- IntegrityCheckResult result = IntegrityCheckResult.allow();
- if (!result.getMatchedRules().isEmpty() || DEBUG_INTEGRITY_COMPONENT) {
- Slog.i(
- TAG,
- String.format(
- "Integrity check of %s result: %s due to %s",
- packageName, result.getEffect(), result.getMatchedRules()));
- }
-
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId,
- result.getEffect() == IntegrityCheckResult.Effect.ALLOW
- ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
- : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
- } catch (IllegalArgumentException e) {
- // This exception indicates something is wrong with the input passed by package manager.
- // e.g., someone trying to trick the system. We block installs in this case.
- Slog.e(TAG, "Invalid input to integrity verification", e);
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
- } catch (Exception e) {
- // Other exceptions indicate an error within the integrity component implementation and
- // we allow them.
- Slog.e(TAG, "Error handling integrity verification", e);
- mPackageManagerInternal.setIntegrityVerificationResult(
- verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
- }
- }
-
- /**
- * Verify the UID and return the installer package name.
- *
- * @return the package name of the installer, or null if it cannot be determined or it is
- * installed via adb.
- */
- @Nullable
- private String getInstallerPackageName(Intent intent) {
- String installer =
- intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
- if (PackageManagerServiceUtils.isInstalledByAdb(installer)) {
- return ADB_INSTALLER;
- }
- int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
- if (installerUid < 0) {
- Slog.e(
- TAG,
- "Installer cannot be determined: installer: "
- + installer
- + " installer UID: "
- + installerUid);
- return UNKNOWN_INSTALLER;
- }
-
- // Verify that the installer UID actually contains the package. Note that comparing UIDs
- // is not safe since context's uid can change in different settings; e.g. Android Auto.
- if (!getPackageListForUid(installerUid).contains(installer)) {
- return UNKNOWN_INSTALLER;
- }
-
- // At this time we can trust "installer".
-
- // A common way for apps to install packages is to send an intent to PackageInstaller. In
- // that case, the installer will always show up as PackageInstaller which is not what we
- // want.
- if (PACKAGE_INSTALLER.contains(installer)) {
- int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
- if (originatingUid < 0) {
- Slog.e(TAG, "Installer is package installer but originating UID not found.");
- return UNKNOWN_INSTALLER;
- }
- List<String> installerPackages = getPackageListForUid(originatingUid);
- if (installerPackages.isEmpty()) {
- Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
- return UNKNOWN_INSTALLER;
- }
- // In the case of multiple package sharing a UID, we just return the first one.
- return installerPackages.get(0);
- }
-
- return installer;
+ mPackageManagerInternal.setIntegrityVerificationResult(
+ verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
}
/** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
@@ -422,264 +224,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
}
}
- private List<String> getInstallerCertificateFingerprint(String installer) {
- if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
- return Collections.emptyList();
- }
- var installerPkg = mPackageManagerInternal.getPackage(installer);
- if (installerPkg == null) {
- Slog.w(TAG, "Installer package " + installer + " not found.");
- return Collections.emptyList();
- }
- return getCertificateFingerprint(installerPkg.getPackageName(),
- installerPkg.getSigningDetails());
- }
-
- private List<String> getCertificateFingerprint(@NonNull String packageName,
- @NonNull SigningDetails signingDetails) {
- ArrayList<String> certificateFingerprints = new ArrayList();
- for (Signature signature : getSignatures(packageName, signingDetails)) {
- certificateFingerprints.add(getFingerprint(signature));
- }
- return certificateFingerprints;
- }
-
- private List<String> getCertificateLineage(@NonNull String packageName,
- @NonNull SigningDetails signingDetails) {
- ArrayList<String> certificateLineage = new ArrayList();
- for (Signature signature : getSignatureLineage(packageName, signingDetails)) {
- certificateLineage.add(getFingerprint(signature));
- }
- return certificateLineage;
- }
-
- /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
- private Map<String, String> getAllowedInstallers(@Nullable Bundle metaData) {
- Map<String, String> packageCertMap = new HashMap<>();
- if (metaData != null) {
- String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME);
- if (allowedInstallers != null) {
- // parse the metadata for certs.
- String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER);
- for (String packageCertPair : installerCertPairs) {
- String[] packageAndCert =
- packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER);
- if (packageAndCert.length == 2) {
- String packageName = getPackageNameNormalized(packageAndCert[0]);
- String cert = packageAndCert[1];
- packageCertMap.put(packageName, cert);
- } else if (packageAndCert.length == 1) {
- packageCertMap.put(
- getPackageNameNormalized(packageAndCert[0]),
- INSTALLER_CERTIFICATE_NOT_EVALUATED);
- }
- }
- }
- }
-
- return packageCertMap;
- }
-
- /** Extract the source stamp embedded in the APK, if present. */
- private void extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata) {
- File installationPath = getInstallationPath(dataUri);
- if (installationPath == null) {
- throw new IllegalArgumentException("Installation path is null, package not found");
- }
-
- SourceStampVerificationResult sourceStampVerificationResult;
- if (installationPath.isDirectory()) {
- try (Stream<Path> filesList = Files.list(installationPath.toPath())) {
- List<String> apkFiles =
- filesList
- .map(path -> path.toAbsolutePath().toString())
- .filter(str -> str.endsWith(".apk"))
- .collect(Collectors.toList());
- sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles);
- } catch (IOException e) {
- throw new IllegalArgumentException("Could not read APK directory");
- }
- } else {
- sourceStampVerificationResult =
- SourceStampVerifier.verify(installationPath.getAbsolutePath());
- }
-
- appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent());
- appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified());
- // A verified stamp is set to be trusted.
- appInstallMetadata.setIsStampTrusted(sourceStampVerificationResult.isVerified());
- if (sourceStampVerificationResult.isVerified()) {
- X509Certificate sourceStampCertificate =
- (X509Certificate) sourceStampVerificationResult.getCertificate();
- // Sets source stamp certificate digest.
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] certificateDigest = digest.digest(sourceStampCertificate.getEncoded());
- appInstallMetadata.setStampCertificateHash(getHexDigest(certificateDigest));
- } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
- throw new IllegalArgumentException(
- "Error computing source stamp certificate digest", e);
- }
- }
- }
-
- private static Signature[] getSignatures(@NonNull String packageName,
- @NonNull SigningDetails signingDetails) {
- Signature[] signatures = signingDetails.getSignatures();
- if (signatures == null || signatures.length < 1) {
- throw new IllegalArgumentException("Package signature not found in " + packageName);
- }
-
- // We are only interested in evaluating the active signatures.
- return signatures;
- }
-
- private static Signature[] getSignatureLineage(@NonNull String packageName,
- @NonNull SigningDetails signingDetails) {
- // Obtain the active signatures of the package.
- Signature[] signatureLineage = getSignatures(packageName, signingDetails);
-
- var pastSignatures = signingDetails.getPastSigningCertificates();
- // Obtain the past signatures of the package.
- if (signatureLineage.length == 1 && !ArrayUtils.isEmpty(pastSignatures)) {
- // Merge the signatures and return.
- Signature[] allSignatures =
- new Signature[signatureLineage.length + pastSignatures.length];
- int i;
- for (i = 0; i < signatureLineage.length; i++) {
- allSignatures[i] = signatureLineage[i];
- }
- for (int j = 0; j < pastSignatures.length; j++) {
- allSignatures[i] = pastSignatures[j];
- i++;
- }
- signatureLineage = allSignatures;
- }
-
- return signatureLineage;
- }
-
- private static String getFingerprint(Signature cert) {
- InputStream input = new ByteArrayInputStream(cert.toByteArray());
-
- CertificateFactory factory;
- try {
- factory = CertificateFactory.getInstance("X509");
- } catch (CertificateException e) {
- throw new RuntimeException("Error getting CertificateFactory", e);
- }
- X509Certificate certificate = null;
- try {
- if (factory != null) {
- certificate = (X509Certificate) factory.generateCertificate(input);
- }
- } catch (CertificateException e) {
- throw new RuntimeException("Error getting X509Certificate", e);
- }
-
- if (certificate == null) {
- throw new RuntimeException("X509 Certificate not found");
- }
-
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] publicKey = digest.digest(certificate.getEncoded());
- return getHexDigest(publicKey);
- } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
- throw new IllegalArgumentException("Error error computing fingerprint", e);
- }
- }
-
- @Nullable
- private Pair<SigningDetails, Bundle> getPackageSigningAndMetadata(Uri dataUri) {
- File installationPath = getInstallationPath(dataUri);
- if (installationPath == null) {
- throw new IllegalArgumentException("Installation path is null, package not found");
- }
-
- try (PackageParser2 parser = mParserSupplier.get()) {
- var pkg = parser.parsePackage(installationPath, 0, false);
- // APK signatures is already verified elsewhere in PackageManager. We do not need to
- // verify it again since it could cause a timeout for large APKs.
- final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
- input, pkg, /* skipVerify= */ true);
- if (result.isError()) {
- Slog.w(TAG, result.getErrorMessage(), result.getException());
- return null;
- }
- return Pair.create(result.getResult(), pkg.getMetaData());
- } catch (Exception e) {
- Slog.w(TAG, "Exception reading " + dataUri, e);
- return null;
- }
- }
-
- private PackageInfo getMultiApkInfo(File multiApkDirectory) {
- // The base apk will normally be called base.apk
- File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
- PackageInfo basePackageInfo =
- mContext.getPackageManager()
- .getPackageArchiveInfo(
- baseFile.getAbsolutePath(),
- PackageManager.GET_SIGNING_CERTIFICATES
- | PackageManager.GET_META_DATA);
-
- if (basePackageInfo == null) {
- for (File apkFile : multiApkDirectory.listFiles()) {
- if (apkFile.isDirectory()) {
- continue;
- }
-
- // If we didn't find a base.apk, then try to parse each apk until we find the one
- // that succeeds.
- try {
- basePackageInfo =
- mContext.getPackageManager()
- .getPackageArchiveInfo(
- apkFile.getAbsolutePath(),
- PackageManager.GET_SIGNING_CERTIFICATES
- | PackageManager.GET_META_DATA);
- } catch (Exception e) {
- // Some of the splits may not contain a valid android manifest. It is an
- // expected exception. We still log it nonetheless but we should keep looking.
- Slog.w(TAG, "Exception reading " + apkFile, e);
- }
- if (basePackageInfo != null) {
- Slog.i(TAG, "Found package info from " + apkFile);
- break;
- }
- }
- }
-
- if (basePackageInfo == null) {
- throw new IllegalArgumentException(
- "Base package info cannot be found from installation directory");
- }
-
- return basePackageInfo;
- }
-
- private File getInstallationPath(Uri dataUri) {
- if (dataUri == null) {
- throw new IllegalArgumentException("Null data uri");
- }
-
- String scheme = dataUri.getScheme();
- if (!"file".equalsIgnoreCase(scheme)) {
- throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
- }
-
- File installationPath = new File(dataUri.getPath());
- if (!installationPath.exists()) {
- throw new IllegalArgumentException("Cannot find file for " + dataUri);
- }
- if (!installationPath.canRead()) {
- throw new IllegalArgumentException("Cannot read file for " + dataUri);
- }
- return installationPath;
- }
-
private String getCallerPackageNameOrThrow(int callingUid) {
String callerPackageName = getCallingRulePusherPackageName(callingUid);
if (callerPackageName == null) {
@@ -715,15 +259,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
return allowedCallingPackages.isEmpty() ? null : allowedCallingPackages.get(0);
}
- private boolean isRuleProvider(String installerPackageName) {
- for (String ruleProvider : getAllowedRuleProviderSystemApps()) {
- if (ruleProvider.matches(installerPackageName)) {
- return true;
- }
- }
- return false;
- }
-
private List<String> getAllowedRuleProviderSystemApps() {
List<String> integrityRuleProviders =
Arrays.asList(
@@ -751,14 +286,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
}
}
- private boolean integrityCheckIncludesRuleProvider() {
- return Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
- 0)
- == 1;
- }
-
private List<String> getPackageListForUid(int uid) {
try {
return Arrays.asList(mContext.getPackageManager().getPackagesForUid(uid));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index d1d5d4868c6f..27bc1cf3e631 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -677,24 +677,15 @@ class MediaRouter2ServiceImpl {
@NonNull IMediaRouter2Manager manager,
int requestId,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiatorPackageName) {
+ @NonNull MediaRoute2Info route) {
Objects.requireNonNull(manager, "manager must not be null");
Objects.requireNonNull(oldSession, "oldSession must not be null");
Objects.requireNonNull(route, "route must not be null");
- Objects.requireNonNull(transferInitiatorUserHandle);
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionWithManagerLocked(
- requestId,
- manager,
- oldSession,
- route,
- transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1738,9 +1729,7 @@ class MediaRouter2ServiceImpl {
int requestId,
@NonNull IMediaRouter2Manager manager,
@NonNull RoutingSessionInfo oldSession,
- @NonNull MediaRoute2Info route,
- @NonNull UserHandle transferInitiatorUserHandle,
- @NonNull String transferInitiatorPackageName) {
+ @NonNull MediaRoute2Info route) {
ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
if (managerRecord == null) {
return;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 363b8e4228b0..68e195d7f079 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -607,16 +607,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
IMediaRouter2Manager manager,
int requestId,
RoutingSessionInfo oldSession,
- MediaRoute2Info route,
- UserHandle transferInitiatorUserHandle,
- String transferInitiatorPackageName) {
- mService2.requestCreateSessionWithManager(
- manager,
- requestId,
- oldSession,
- route,
- transferInitiatorUserHandle,
- transferInitiatorPackageName);
+ MediaRoute2Info route) {
+ mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
}
// Binder call
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index e7e519ede768..e0913ccbc7f7 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -28,6 +28,7 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -73,6 +74,7 @@ import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
@@ -195,6 +197,15 @@ public final class MediaProjectionManagerService extends SystemService
if (mProjectionGrant == null || mProjectionGrant.packageName == null) {
return false;
}
+ boolean disableScreenShareProtections = Settings.Global.getInt(
+ getContext().getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
+ if (disableScreenShareProtections) {
+ Slog.v(TAG,
+ "Allowing keyguard capture as screenshare protections are disabled.");
+ return true;
+ }
+
if (mPackageManager.checkPermission(RECORD_SENSITIVE_CONTENT,
mProjectionGrant.packageName)
== PackageManager.PERMISSION_GRANTED) {
@@ -226,7 +237,8 @@ public final class MediaProjectionManagerService extends SystemService
void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
if (!isKeyguardLocked) return;
synchronized (mLock) {
- if (mProjectionGrant != null && !canCaptureKeyguard()) {
+ if (mProjectionGrant != null && !canCaptureKeyguard()
+ && mProjectionGrant.mVirtualDisplayId != INVALID_DISPLAY) {
Slog.d(TAG, "Content Recording: Stopped MediaProjection"
+ " due to keyguard lock");
mProjectionGrant.stop();
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index a24c743929b5..f79d9ef174ea 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -165,6 +165,13 @@ flag {
}
flag {
+ name: "notification_lock_screen_settings"
+ namespace: "systemui"
+ description: "This flag enables the new settings page for the notifications on lock screen."
+ bug: "367455695"
+}
+
+flag {
name: "notification_vibration_in_sound_uri"
namespace: "systemui"
description: "This flag enables sound uri with vibration source"
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 369029adac59..84a5f2b0e8bc 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -80,11 +80,6 @@ import java.util.function.BiFunction;
*/
public final class BroadcastHelper {
private static final boolean DEBUG_BROADCASTS = false;
- /**
- * Permissions required in order to receive instant application lifecycle broadcasts.
- */
- private static final String[] INSTANT_APP_BROADCAST_PERMISSION =
- new String[]{android.Manifest.permission.ACCESS_INSTANT_APPS};
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -115,7 +110,7 @@ public final class BroadcastHelper {
SparseArray<int[]> broadcastAllowList = new SparseArray<>();
broadcastAllowList.put(userId, visibilityAllowList);
broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, null /* requiredPermissions */);
}
void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
@@ -123,7 +118,7 @@ public final class BroadcastHelper {
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
+ @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) {
try {
final IActivityManager am = ActivityManager.getService();
if (am == null) return;
@@ -137,12 +132,12 @@ public final class BroadcastHelper {
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, requiredPermissions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true /* isInstantApp */, null,
- null /* filterExtrasForReceiver */, bOptions);
+ instantUserIds, true /* isInstantApp */, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, bOptions, requiredPermissions);
}
} catch (RemoteException ex) {
}
@@ -166,7 +161,8 @@ public final class BroadcastHelper {
boolean isInstantApp,
@Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
+ @Nullable Bundle bOptions,
+ @Nullable String[] requiredPermissions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
@@ -189,17 +185,18 @@ public final class BroadcastHelper {
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList,
- filterExtrasForReceiver, bOptions);
+ filterExtrasForReceiver, bOptions, requiredPermissions);
}
}
-
private void broadcastIntent(Intent intent, IIntentReceiver finishedReceiver,
boolean isInstantApp, int userId, @Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
- @Nullable Bundle bOptions) {
- final String[] requiredPermissions =
- isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
+ @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) {
+ if (isInstantApp) {
+ requiredPermissions = ArrayUtils.appendElement(String.class, requiredPermissions,
+ android.Manifest.permission.ACCESS_INSTANT_APPS);
+ }
if (DEBUG_BROADCASTS) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
@@ -234,7 +231,7 @@ public final class BroadcastHelper {
null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
snapshot, callingUid, intentExtras),
- null /* bOptions */);
+ null /* bOptions */, null /* requiredPermissions */);
}
/**
@@ -294,14 +291,29 @@ public final class BroadcastHelper {
return bOptions;
}
- private void sendPackageChangedBroadcast(@NonNull String packageName,
- boolean dontKillApp,
- @NonNull ArrayList<String> componentNames,
- int packageUid,
- @Nullable String reason,
- @Nullable int[] userIds,
- @Nullable int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList) {
+ private void sendPackageChangedBroadcastInternal(@NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @Nullable String reason,
+ @Nullable int[] userIds,
+ @Nullable int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList) {
+ sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
+ packageUid, reason, userIds, instantUserIds, broadcastAllowList,
+ null /* targetPackageName */, null /* requiredPermissions */);
+ }
+
+ private void sendPackageChangedBroadcastWithPermissions(@NonNull String packageName,
+ boolean dontKillApp,
+ @NonNull ArrayList<String> componentNames,
+ int packageUid,
+ @Nullable String reason,
+ @Nullable int[] userIds,
+ @Nullable int[] instantUserIds,
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable String targetPackageName,
+ @Nullable String[] requiredPermissions) {
if (DEBUG_INSTALL) {
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
@@ -321,9 +333,10 @@ public final class BroadcastHelper {
// little component state change.
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
- sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
- userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
- null /* bOptions */);
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags,
+ targetPackageName, null /* finishedReceiver */, userIds, instantUserIds,
+ broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */,
+ requiredPermissions);
}
static void sendDeviceCustomizationReadyBroadcast() {
@@ -680,7 +693,8 @@ public final class BroadcastHelper {
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
- broadcastAllowlist, null /* filterExtrasForReceiver */, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
// Send to PermissionController for all new users, even if it may not be running for some
// users
if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
@@ -688,7 +702,8 @@ public final class BroadcastHelper {
packageName, extras, 0,
mContext.getPackageManager().getPermissionControllerPackageName(),
null, userIds, instantUserIds,
- broadcastAllowlist, null /* filterExtrasForReceiver */, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
}
@@ -719,7 +734,8 @@ public final class BroadcastHelper {
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
- null /* filterExtrasForReceiver */, null);
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
/**
@@ -824,7 +840,7 @@ public final class BroadcastHelper {
final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY;
final SparseArray<int[]> broadcastAllowList =
isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
- mHandler.post(() -> sendPackageChangedBroadcast(
+ mHandler.post(() -> sendPackageChangedBroadcastInternal(
packageName, dontKillApp, componentNames, packageUid, reason, userIds,
instantUserIds, broadcastAllowList));
mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
@@ -843,7 +859,7 @@ public final class BroadcastHelper {
@Nullable Bundle bOptions) {
mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags,
targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- null /* filterExtrasForReceiver */, bOptions));
+ null /* filterExtrasForReceiver */, bOptions, null /* requiredPermissions */));
if (targetPkg == null) {
// For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called
// many times to different targets, e.g. installer app, permission controller, other
@@ -1014,7 +1030,7 @@ public final class BroadcastHelper {
extras, flags, null /* targetPkg */, null /* finishedReceiver */,
new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
filterExtrasForReceiver,
- options));
+ options, null /* requiredPermissions */));
notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
}
@@ -1046,9 +1062,12 @@ public final class BroadcastHelper {
} else {
intentExtras = null;
}
- doSendBroadcast(action, null, intentExtras,
- Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null, null);
+ doSendBroadcast(action, null /* pkg */, intentExtras,
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName,
+ null /* finishedReceiver */,
+ targetUserIds, false /* isInstantApp */, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
});
}
@@ -1077,7 +1096,7 @@ public final class BroadcastHelper {
null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
snapshot, callingUid, intentExtras),
- null /* bOptions */));
+ null /* bOptions */, null /* requiredPermissions */));
}
void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot,
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 9e8598a04a98..0b58c759b284 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -791,16 +791,6 @@ public final class DexOptHelper {
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
-
- // This mirrors logic from commitReconciledScanResultLocked, where the library
- // files needed for dexopt are assigned.
- PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
- // Unfortunately, the updated system app flag is only tracked on this
- // PackageSetting
- boolean isUpdatedSystemApp =
- installRequest.getScannedPackageSetting().isUpdatedSystemApp();
- realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
-
DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
installRequest, dexoptOptions);
installRequest.onDexoptFinished(dexOptResult);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 34d939b07187..f6a808b6c33e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -25,12 +25,14 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVI
import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+import static com.android.server.pm.PackageInstallerSession.isValidVerificationPolicy;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -150,6 +152,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntPredicate;
import java.util.function.Supplier;
@@ -275,6 +278,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
};
+ /**
+ * Default verification policy for incoming installation sessions.
+ * TODO(b/360129657): update the default policy.
+ */
+ private final AtomicInteger mVerificationPolicy = new AtomicInteger(
+ VERIFICATION_POLICY_BLOCK_FAIL_WARN);
+
private static final class Lifecycle extends SystemService {
private final PackageInstallerService mPackageInstallerService;
@@ -1042,7 +1052,7 @@ 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);
+ mVerifierController, mVerificationPolicy.get());
synchronized (mSessions) {
mSessions.put(sessionId, session);
@@ -1866,6 +1876,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ @Override
+ public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the "
+ + "com.android.permission.VERIFICATION_AGENT permission "
+ + "to get the verification policy");
+ }
+ return mVerificationPolicy.get();
+ }
+
+ @Override
+ public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the "
+ + "com.android.permission.VERIFICATION_AGENT permission "
+ + "to set the verification policy");
+ }
+ if (!isValidVerificationPolicy(policy)) {
+ return false;
+ }
+ if (policy != mVerificationPolicy.get()) {
+ mVerificationPolicy.set(policy);
+ }
+ return true;
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9e0ba8492ab9..04d0d182d3b2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,9 +21,17 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTA
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO;
import static android.content.pm.DataLoaderType.INCREMENTAL;
import static android.content.pm.DataLoaderType.STREAMING;
+import static android.content.pm.PackageInstaller.EXTRA_VERIFICATION_FAILURE_REASON;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
+import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE;
+import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED;
+import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_UNKNOWN;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_OPEN;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_NONE;
import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
@@ -38,7 +46,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAIL
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
+import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE;
import static android.os.Process.INVALID_UID;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static android.system.OsConstants.O_CREAT;
@@ -313,6 +321,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
"applicationEnabledSettingPersistent";
private static final String ATTR_DOMAIN = "domain";
+ private static final String ATTR_VERIFICATION_POLICY = "verificationPolicy";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -410,6 +419,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final PackageSessionProvider mSessionProvider;
private final SilentUpdatePolicy mSilentUpdatePolicy;
/**
+ * The verification policy applied to this session, which might be different from the default
+ * verification policy used by the system.
+ */
+ private final AtomicInteger mVerificationPolicy;
+ /**
* Note all calls must be done outside {@link #mLock} to prevent lock inversion.
*/
private final StagingManager mStagingManager;
@@ -791,7 +805,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (errorMsg != null) {
Slog.e(TAG, "verifySession error: " + errorMsg);
setSessionFailed(INSTALL_FAILED_INTERNAL_ERROR, errorMsg);
- onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, errorMsg);
+ onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, errorMsg,
+ /* extras= */ null);
return false;
}
return true;
@@ -1167,7 +1182,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int sessionErrorCode,
String sessionErrorMessage, DomainSet preVerifiedDomains,
- @NonNull VerifierController verifierController) {
+ @NonNull VerifierController verifierController,
+ @PackageInstaller.VerificationPolicy int verificationPolicy) {
mCallback = callback;
mContext = context;
mPm = pm;
@@ -1177,6 +1193,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mHandler = new Handler(looper, mHandlerCallback);
mStagingManager = stagingManager;
mVerifierController = verifierController;
+ mVerificationPolicy = new AtomicInteger(verificationPolicy);
this.sessionId = sessionId;
this.userId = userId;
@@ -2580,10 +2597,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
dispatchSessionFinished(error, detailMessage, null);
}
- private void onSessionVerificationFailure(int error, String msg) {
+ private void onSessionVerificationFailure(int error, String msg, Bundle extras) {
Slog.e(TAG, "Failed to verify session " + sessionId);
// Dispatch message to remove session from PackageInstallerService.
- dispatchSessionFinished(error, msg, null);
+ dispatchSessionFinished(error, msg, extras);
maybeFinishChildSessions(error, msg);
}
@@ -2856,7 +2873,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg);
setSessionFailed(e.error, errorMsg);
- onSessionVerificationFailure(e.error, errorMsg);
+ onSessionVerificationFailure(e.error, errorMsg, /* extras= */ null);
}
if (Flags.verificationService()) {
final Supplier<Computer> snapshotSupplier = mPm::snapshotComputer;
@@ -2872,11 +2889,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// the installation can proceed.
if (!mVerifierController.startVerificationSession(snapshotSupplier, userId,
sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo,
- declaredLibraries, /* extensionParams= */ null,
+ declaredLibraries, mVerificationPolicy.get(), /* extensionParams= */ null,
new VerifierCallback(), /* retry= */ false)) {
// A verifier is installed but cannot be connected. Installation disallowed.
onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
- "A verifier agent is available on device but cannot be connected.");
+ "A verifier agent is available on device but cannot be connected.",
+ /* extras= */ null);
}
} else {
// Verifier is not installed. Let the installation pass for now.
@@ -2917,7 +2935,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg);
setSessionFailed(e.error, errorMsg);
- onSessionVerificationFailure(e.error, errorMsg);
+ onSessionVerificationFailure(e.error, errorMsg, /* extras= */ null);
}
}
@@ -2926,24 +2944,57 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
public class VerifierCallback {
/**
+ * Called by the VerifierController when the verifier requests to get the current
+ * verification policy for this session.
+ */
+ public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
+ return mVerificationPolicy.get();
+ }
+ /**
+ * Called by the VerifierController when the verifier requests to change the verification
+ * policy for this session.
+ */
+ public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
+ if (!isValidVerificationPolicy(policy)) {
+ return false;
+ }
+ mVerificationPolicy.set(policy);
+ return true;
+ }
+ /**
* Called by the VerifierController when the connection has failed.
*/
public void onConnectionFailed() {
- mHandler.post(() -> {
- onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
- "A verifier agent is available on device but cannot be connected.");
- });
+ // TODO(b/360129657): prompt user on fail warning
+ handleNonPackageBlockedFailure(
+ /* onFailWarning= */ PackageInstallerSession.this::resumeVerify,
+ /* onFailClosed= */ () -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON,
+ VERIFICATION_FAILED_REASON_UNKNOWN);
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "A verifier agent is available on device but cannot be connected.",
+ bundle);
+
+ });
}
/**
* Called by the VerifierController when the verification request has timed out.
*/
public void onTimeout() {
- mHandler.post(() -> {
- mVerifierController.notifyVerificationTimeout(sessionId);
- onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
- "Verification timed out; missing a response from the verifier within the"
- + " time limit");
- });
+ // Always notify the verifier, regardless of the policy.
+ mVerifierController.notifyVerificationTimeout(sessionId);
+ // TODO(b/360129657): prompt user on fail warning
+ handleNonPackageBlockedFailure(
+ /* onFailWarning= */ PackageInstallerSession.this::resumeVerify,
+ /* onFailClosed= */ () -> {
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON,
+ VERIFICATION_FAILED_REASON_UNKNOWN);
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "Verification timed out; missing a response from the verifier"
+ + " within the time limit", bundle);
+ });
}
/**
* Called by the VerifierController when the verification request has received a complete
@@ -2953,17 +3004,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Nullable PersistableBundle extensionResponse) {
// TODO: handle extension response
mHandler.post(() -> {
- if (statusReceived.isVerified()) {
+ if (statusReceived.isVerified()
+ || mVerificationPolicy.get() == VERIFICATION_POLICY_NONE) {
// Continue with the rest of the verification and installation.
resumeVerify();
- } else {
- StringBuilder sb = new StringBuilder("Verifier rejected the installation");
- if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) {
- sb.append(" with message: ").append(statusReceived.getFailureMessage());
- }
- onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
- sb.toString());
+ return;
+ }
+ // Package is blocked.
+ StringBuilder sb = new StringBuilder("Verifier rejected the installation");
+ if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) {
+ sb.append(" with message: ").append(statusReceived.getFailureMessage());
}
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON,
+ VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED);
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ sb.toString(), bundle);
});
}
/**
@@ -2971,16 +3027,51 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
* response.
*/
public void onVerificationIncompleteReceived(int incompleteReason) {
- mHandler.post(() -> {
- if (incompleteReason == VERIFICATION_INCOMPLETE_UNKNOWN) {
- // TODO: change this to a user confirmation and handle other incomplete reasons
- onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
- "Verification cannot be completed for unknown reasons.");
+ // TODO(b/360129657): prompt user on fail warning
+ handleNonPackageBlockedFailure(
+ /* onFailWarning= */ PackageInstallerSession.this::resumeVerify,
+ /* onFailClosed= */ () -> {
+ final int failureReason;
+ StringBuilder sb = new StringBuilder(
+ "Verification cannot be completed because of ");
+ if (incompleteReason == VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE) {
+ failureReason = VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE;
+ sb.append("unavailable network.");
+ } else {
+ failureReason = VERIFICATION_FAILED_REASON_UNKNOWN;
+ sb.append("unknown reasons.");
+ }
+ Bundle bundle = new Bundle();
+ bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON, failureReason);
+ onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
+ sb.toString(), bundle);
+ });
+ }
+
+ private void handleNonPackageBlockedFailure(Runnable onFailWarning, Runnable onFailClosed) {
+ final Runnable r = switch (mVerificationPolicy.get()) {
+ case VERIFICATION_POLICY_NONE, VERIFICATION_POLICY_BLOCK_FAIL_OPEN ->
+ PackageInstallerSession.this::resumeVerify;
+ case VERIFICATION_POLICY_BLOCK_FAIL_WARN -> onFailWarning;
+ case VERIFICATION_POLICY_BLOCK_FAIL_CLOSED -> onFailClosed;
+ default -> {
+ Log.wtf(TAG, "Unknown verification policy: " + mVerificationPolicy.get());
+ yield onFailClosed;
}
- });
+ };
+ mHandler.post(r);
}
}
+ /**
+ * Returns whether a policy is a valid verification policy.
+ */
+ public static boolean isValidVerificationPolicy(
+ @PackageInstaller.VerificationPolicy int policy) {
+ return policy >= VERIFICATION_POLICY_NONE
+ && policy <= VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
+ }
+
private IntentSender getRemoteStatusReceiver() {
synchronized (mLock) {
return mRemoteStatusReceiver;
@@ -3156,7 +3247,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (error == INSTALL_SUCCEEDED) {
onVerificationComplete();
} else {
- onSessionVerificationFailure(error, msg);
+ onSessionVerificationFailure(error, msg, /* extras= */ null);
}
});
});
@@ -5328,6 +5419,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ /**
+ * @return the current policy for the verification request associated with this session.
+ */
+ @VisibleForTesting
+ public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
+ assertCallerIsOwnerOrRoot();
+ return mVerificationPolicy.get();
+ }
void setSessionReady() {
synchronized (mLock) {
@@ -5631,6 +5730,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (!ArrayUtils.isEmpty(warnings)) {
fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings);
}
+ if (extras.containsKey(EXTRA_VERIFICATION_FAILURE_REASON)) {
+ fillIn.putExtra(EXTRA_VERIFICATION_FAILURE_REASON,
+ extras.getInt(EXTRA_VERIFICATION_FAILURE_REASON));
+ }
}
try {
final BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -5786,6 +5889,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason);
writeBooleanAttribute(out, ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT,
params.applicationEnabledSettingPersistent);
+ out.attributeInt(null, ATTR_VERIFICATION_POLICY, mVerificationPolicy.get());
final boolean isDataLoader = params.dataLoaderParams != null;
writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
@@ -5936,6 +6040,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final boolean sealed = in.getAttributeBoolean(null, ATTR_SEALED, false);
final int parentSessionId = in.getAttributeInt(null, ATTR_PARENT_SESSION_ID,
SessionInfo.INVALID_ID);
+ final int verificationPolicy = in.getAttributeInt(null, ATTR_VERIFICATION_POLICY,
+ VERIFICATION_POLICY_NONE);
final SessionParams params = new SessionParams(
SessionParams.MODE_INVALID);
@@ -6110,6 +6216,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
installerUid, installSource, params, createdMillis, committedMillis, stageDir,
stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController);
+ sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController,
+ verificationPolicy);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 455776993c56..d78f12217271 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4709,10 +4709,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
- packageName, extras,
- Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY,
+ null /* targetPkg */, null /* finishedReceiver */, userIds,
+ null /* instantUserIds */, broadcastAllowList,
+ null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
});
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
@@ -7169,17 +7170,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
- packageName, extras,
- flags, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, flags, null /* targetPkg */,
+ null /* finishedReceiver */, userIds, null /* instantUserIds */,
+ broadcastAllowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */, null /* requiredPermissions */);
});
} else {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
- packageName, extras,
- flags, null, null,
- userIds, null, broadcastAllowList, null,
- null);
+ packageName, extras, flags, null /* targetPkg */,
+ null /* finishedReceiver */, userIds, null /* instantUserIds */,
+ broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */,
+ null /* requiredPermissions */);
}
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras, userIds, null /* instantUserIds */,
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
index 7eac940933c2..b7cc7ccead89 100644
--- a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
@@ -30,6 +30,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
@@ -94,9 +95,22 @@ public class VerifierController {
// Max duration allowed to wait for a verifier to respond to a verification request.
private static final long DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS =
TimeUnit.MINUTES.toMillis(10);
+ /**
+ * Configurable maximum amount of time in milliseconds for the system to wait from the moment
+ * when the installation session requires a verification, till when the request is delivered to
+ * the verifier, pending the connection to be established. If the request has not been delivered
+ * to the verifier within this amount of time, e.g., because the verifier has crashed or ANR'd,
+ * the controller then sends a failure status back to the installation session.
+ * Flag type: {@code long}
+ * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+ */
+ private static final String PROPERTY_VERIFIER_CONNECTION_TIMEOUT_MILLIS =
+ "verifier_connection_timeout_millis";
// The maximum amount of time to wait from the moment when the session requires a verification,
// till when the request is delivered to the verifier, pending the connection to be established.
- private static final long CONNECTION_TIMEOUT_SECONDS = 10;
+ private static final long DEFAULT_VERIFIER_CONNECTION_TIMEOUT_MILLIS =
+ TimeUnit.SECONDS.toMillis(10);
+
// The maximum amount of time to wait before the system unbinds from the verifier.
private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
@@ -271,6 +285,7 @@ public class VerifierController {
int installationSessionId, String packageName,
Uri stagedPackageUri, SigningInfo signingInfo,
List<SharedLibraryInfo> declaredLibraries,
+ @PackageInstaller.VerificationPolicy int verificationPolicy,
PersistableBundle extensionParams, PackageInstallerSession.VerifierCallback callback,
boolean retry) {
// Try connecting to the verifier if not already connected
@@ -292,7 +307,7 @@ public class VerifierController {
/* id= */ verificationId,
/* installSessionId= */ installationSessionId,
packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
- new VerificationSessionInterface(),
+ verificationPolicy, new VerificationSessionInterface(callback),
new VerificationSessionCallback(callback));
AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
if (!retry) {
@@ -306,7 +321,8 @@ public class VerifierController {
}
service.onVerificationRetry(session);
}
- }).orTimeout(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS).whenComplete((res, err) -> {
+ }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
+ .whenComplete((res, err) -> {
if (err != null) {
Slog.e(TAG, "Error notifying verification request for session " + verificationId,
err);
@@ -407,6 +423,12 @@ public class VerifierController {
// This class handles requests from the remote verifier
private class VerificationSessionInterface extends IVerificationSessionInterface.Stub {
+ private final PackageInstallerSession.VerifierCallback mCallback;
+
+ VerificationSessionInterface(PackageInstallerSession.VerifierCallback callback) {
+ mCallback = callback;
+ }
+
@Override
public long getTimeoutTime(int verificationId) {
checkCallerPermission();
@@ -432,6 +454,20 @@ public class VerifierController {
return tracker.extendTimeRemaining(additionalMs);
}
}
+
+ @Override
+ public boolean setVerificationPolicy(int verificationId,
+ @PackageInstaller.VerificationPolicy int policy) {
+ checkCallerPermission();
+ synchronized (mVerificationStatus) {
+ final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
+ if (tracker == null) {
+ throw new IllegalStateException("Verification session " + verificationId
+ + " doesn't exist or has finished");
+ }
+ }
+ return mCallback.setVerificationPolicy(policy);
+ }
}
private class VerificationSessionCallback extends IVerificationSessionCallback.Stub {
@@ -451,8 +487,8 @@ public class VerifierController {
throw new IllegalStateException("Verification session " + id
+ " doesn't exist or has finished");
}
- mCallback.onVerificationIncompleteReceived(reason);
}
+ mCallback.onVerificationIncompleteReceived(reason);
// Remove status tracking and stop the timeout countdown
removeStatusTracker(id);
}
@@ -630,6 +666,14 @@ public class VerifierController {
return getMaxVerificationExtendedTimeoutMillisFromDeviceConfig();
}
+ /**
+ * This is added so that we can mock the maximum connection timeout duration without
+ * calling into DeviceConfig.
+ */
+ public long getVerifierConnectionTimeoutMillis() {
+ return getVerifierConnectionTimeoutMillisFromDeviceConfig();
+ }
+
private static long getVerificationRequestTimeoutMillisFromDeviceConfig() {
return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
PROPERTY_VERIFICATION_REQUEST_TIMEOUT_MILLIS,
@@ -641,5 +685,11 @@ public class VerifierController {
PROPERTY_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS,
DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS);
}
+
+ private static long getVerifierConnectionTimeoutMillisFromDeviceConfig() {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ PROPERTY_VERIFIER_CONNECTION_TIMEOUT_MILLIS,
+ DEFAULT_VERIFIER_CONNECTION_TIMEOUT_MILLIS);
+ }
}
}
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 8f603fc34b32..075a31f3b24c 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -191,8 +191,7 @@ public class RotationResolverManagerService extends
SensorPrivacyManager.Sensors.CAMERA);
if (mIsServiceEnabled && isCameraAvailable) {
final RotationResolverManagerPerUserService service =
- getServiceForUserLocked(
- UserHandle.getCallingUserId());
+ getServiceForUserLocked(UserHandle.USER_CURRENT);
final RotationResolutionRequest request;
if (packageName == null) {
request = new RotationResolutionRequest(/* packageName */ "",
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
new file mode 100644
index 000000000000..07639d1a3945
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -0,0 +1,294 @@
+/*
+ * 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.security.forensic;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.security.forensic.IForensicService;
+import android.security.forensic.IForensicServiceCommandCallback;
+import android.security.forensic.IForensicServiceStateCallback;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class ForensicService extends SystemService {
+ private static final String TAG = "ForensicService";
+
+ private static final int MSG_MONITOR_STATE = 0;
+ private static final int MSG_MAKE_VISIBLE = 1;
+ private static final int MSG_MAKE_INVISIBLE = 2;
+ private static final int MSG_ENABLE = 3;
+ private static final int MSG_DISABLE = 4;
+ private static final int MSG_BACKUP = 5;
+
+ private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
+ private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
+ private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE;
+ private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;
+
+ private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
+ private static final int ERROR_PERMISSION_DENIED =
+ IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
+ private static final int ERROR_INVALID_STATE_TRANSITION =
+ IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
+ private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final BinderService mBinderService;
+
+ private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
+ private volatile int mState = STATE_INVISIBLE;
+
+ public ForensicService(@NonNull Context context) {
+ this(new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ ForensicService(@NonNull Injector injector) {
+ super(injector.getContext());
+ mContext = injector.getContext();
+ mHandler = new EventHandler(injector.getLooper(), this);
+ mBinderService = new BinderService(this);
+ }
+
+ @VisibleForTesting
+ protected void setState(int state) {
+ mState = state;
+ }
+
+ private static final class BinderService extends IForensicService.Stub {
+ final ForensicService mService;
+
+ BinderService(ForensicService service) {
+ mService = service;
+ }
+
+ @Override
+ public void monitorState(IForensicServiceStateCallback callback) {
+ mService.mHandler.obtainMessage(MSG_MONITOR_STATE, callback).sendToTarget();
+ }
+
+ @Override
+ public void makeVisible(IForensicServiceCommandCallback callback) {
+ mService.mHandler.obtainMessage(MSG_MAKE_VISIBLE, callback).sendToTarget();
+ }
+
+ @Override
+ public void makeInvisible(IForensicServiceCommandCallback callback) {
+ mService.mHandler.obtainMessage(MSG_MAKE_INVISIBLE, callback).sendToTarget();
+ }
+
+ @Override
+ public void enable(IForensicServiceCommandCallback callback) {
+ mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget();
+ }
+
+ @Override
+ public void disable(IForensicServiceCommandCallback callback) {
+ mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget();
+ }
+ }
+
+ private static class EventHandler extends Handler {
+ private final ForensicService mService;
+
+ EventHandler(Looper looper, ForensicService service) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_MONITOR_STATE:
+ try {
+ mService.monitorState(
+ (IForensicServiceStateCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_MAKE_VISIBLE:
+ try {
+ mService.makeVisible((IForensicServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_MAKE_INVISIBLE:
+ try {
+ mService.makeInvisible((IForensicServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_ENABLE:
+ try {
+ mService.enable((IForensicServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ case MSG_DISABLE:
+ try {
+ mService.disable((IForensicServiceCommandCallback) msg.obj);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException", e);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ }
+ }
+
+ private void monitorState(IForensicServiceStateCallback callback) throws RemoteException {
+ for (int i = 0; i < mStateMonitors.size(); i++) {
+ if (mStateMonitors.get(i).asBinder() == callback.asBinder()) {
+ return;
+ }
+ }
+ mStateMonitors.add(callback);
+ callback.onStateChange(mState);
+ }
+
+ private void notifyStateMonitors() throws RemoteException {
+ for (int i = 0; i < mStateMonitors.size(); i++) {
+ mStateMonitors.get(i).onStateChange(mState);
+ }
+ }
+
+ private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
+ switch (mState) {
+ case STATE_INVISIBLE:
+ mState = STATE_VISIBLE;
+ notifyStateMonitors();
+ callback.onSuccess();
+ break;
+ case STATE_VISIBLE:
+ callback.onSuccess();
+ break;
+ default:
+ callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ }
+ }
+
+ private void makeInvisible(IForensicServiceCommandCallback callback) throws RemoteException {
+ switch (mState) {
+ case STATE_VISIBLE:
+ case STATE_ENABLED:
+ mState = STATE_INVISIBLE;
+ notifyStateMonitors();
+ callback.onSuccess();
+ break;
+ case STATE_INVISIBLE:
+ callback.onSuccess();
+ break;
+ default:
+ callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ }
+ }
+
+ private void enable(IForensicServiceCommandCallback callback) throws RemoteException {
+ switch (mState) {
+ case STATE_VISIBLE:
+ mState = STATE_ENABLED;
+ notifyStateMonitors();
+ callback.onSuccess();
+ break;
+ case STATE_ENABLED:
+ callback.onSuccess();
+ break;
+ default:
+ callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ }
+ }
+
+ private void disable(IForensicServiceCommandCallback callback) throws RemoteException {
+ switch (mState) {
+ case STATE_ENABLED:
+ mState = STATE_VISIBLE;
+ notifyStateMonitors();
+ callback.onSuccess();
+ break;
+ case STATE_VISIBLE:
+ callback.onSuccess();
+ break;
+ default:
+ callback.onFailure(ERROR_INVALID_STATE_TRANSITION);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.FORENSIC_SERVICE, mBinderService);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Could not start the ForensicService.", t);
+ }
+ }
+
+ @VisibleForTesting
+ IForensicService getBinderService() {
+ return mBinderService;
+ }
+
+ interface Injector {
+ Context getContext();
+
+ Looper getLooper();
+ }
+
+ private static final class InjectorImpl implements Injector {
+ private final Context mContext;
+
+ InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+
+ @Override
+ public Looper getLooper() {
+ ServiceThread serviceThread =
+ new ServiceThread(
+ TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ serviceThread.start();
+ return serviceThread.getLooper();
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 91a17a9e1c31..4589d26261dc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -381,10 +381,12 @@ public final class TvInputManagerService extends SystemService {
// service to populate the hardware list.
serviceState = new ServiceState(component, userId);
userState.serviceStateMap.put(component, serviceState);
- updateServiceConnectionLocked(component, userId);
} else {
inputList.addAll(serviceState.hardwareInputMap.values());
}
+ if (serviceState.needInit) {
+ updateServiceConnectionLocked(component, userId);
+ }
} else {
try {
TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -489,6 +491,27 @@ public final class TvInputManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
+ private void cleanUpHdmiDevices(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "cleanUpHdmiDevices: user " + userId);
+ }
+ UserState userState = getOrCreateUserStateLocked(userId);
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
+ try {
+ if (serviceState.service != null) {
+ serviceState.service.notifyHdmiDeviceRemoved(device);
+ } else {
+ serviceState.hdmiDeviceRemovedBuffer.add(device);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
+ }
+ }
+ }
+ }
+
private void startUser(int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
@@ -500,9 +523,13 @@ public final class TvInputManagerService extends SystemService {
if (userInfo.isProfile()
&& parentInfo != null
&& parentInfo.id == mCurrentUserId) {
- // only the children of the current user can be started in background
+ int prevUserId = mCurrentUserId;
mCurrentUserId = userId;
- startProfileLocked(userId);
+ // only the children of the current user can be started in background
+ releaseSessionOfUserLocked(prevUserId);
+ cleanUpHdmiDevices(prevUserId);
+ unbindServiceOfUserLocked(prevUserId);
+ startProfileLocked(mCurrentUserId);
}
}
}
@@ -515,6 +542,7 @@ public final class TvInputManagerService extends SystemService {
}
releaseSessionOfUserLocked(userId);
+ cleanUpHdmiDevices(userId);
unbindServiceOfUserLocked(userId);
mRunningProfiles.remove(userId);
}
@@ -543,15 +571,19 @@ public final class TvInputManagerService extends SystemService {
unbindServiceOfUserLocked(runningId);
}
mRunningProfiles.clear();
- releaseSessionOfUserLocked(mCurrentUserId);
- unbindServiceOfUserLocked(mCurrentUserId);
+ int prevUserId = mCurrentUserId;
mCurrentUserId = userId;
- buildTvInputListLocked(userId, null);
- buildTvContentRatingSystemListLocked(userId);
+
+ releaseSessionOfUserLocked(prevUserId);
+ cleanUpHdmiDevices(prevUserId);
+ unbindServiceOfUserLocked(prevUserId);
+
+ buildTvInputListLocked(mCurrentUserId, null);
+ buildTvContentRatingSystemListLocked(mCurrentUserId);
mMessageHandler
.obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER,
- getContentResolverForUser(userId))
+ getContentResolverForUser(mCurrentUserId))
.sendToTarget();
}
}
@@ -590,6 +622,9 @@ public final class TvInputManagerService extends SystemService {
@GuardedBy("mLock")
private void unbindServiceOfUserLocked(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "unbindServiceOfUserLocked: user " + userId);
+ }
UserState userState = getUserStateLocked(userId);
if (userState == null) {
return;
@@ -600,7 +635,12 @@ public final class TvInputManagerService extends SystemService {
ServiceState serviceState = userState.serviceStateMap.get(component);
if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
unbindService(serviceState);
- it.remove();
+ if (!serviceState.isHardware) {
+ it.remove();
+ } else {
+ serviceState.hardwareInputMap.clear();
+ serviceState.needInit = true;
+ }
}
}
}
@@ -774,7 +814,7 @@ public final class TvInputManagerService extends SystemService {
boolean shouldBind;
if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
shouldBind = !serviceState.sessionTokens.isEmpty()
- || (serviceState.isHardware && serviceState.neverConnected);
+ || (serviceState.isHardware && serviceState.needInit);
} else {
// For a non-current user,
// if sessionTokens is not empty, it contains recording sessions only
@@ -3404,13 +3444,13 @@ public final class TvInputManagerService extends SystemService {
private ServiceCallback callback;
private boolean bound;
private boolean reconnecting;
- private boolean neverConnected;
+ private boolean needInit;
private ServiceState(ComponentName component, int userId) {
this.component = component;
this.connection = new InputServiceConnection(component, userId);
this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
- this.neverConnected = true;
+ this.needInit = true;
}
}
@@ -3618,11 +3658,9 @@ public final class TvInputManagerService extends SystemService {
}
ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent();
ServiceState serviceState = getServiceStateLocked(component, userId);
- boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
- if (removed) {
- buildTvInputListLocked(userId, null);
- mTvInputHardwareManager.removeHardwareInput(inputId);
- }
+ serviceState.hardwareInputMap.remove(inputId);
+ buildTvInputListLocked(userId, null);
+ mTvInputHardwareManager.removeHardwareInput(inputId);
}
private final class InputServiceConnection implements ServiceConnection {
@@ -3648,7 +3686,7 @@ public final class TvInputManagerService extends SystemService {
}
ServiceState serviceState = userState.serviceStateMap.get(mComponent);
serviceState.service = ITvInputService.Stub.asInterface(service);
- serviceState.neverConnected = false;
+ serviceState.needInit = false;
// Register a callback, if we need to.
if (serviceState.isHardware && serviceState.callback == null) {
@@ -3841,9 +3879,12 @@ public final class TvInputManagerService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId +
- " by " + mComponent + ", userId: " + mUserId);
- removeHardwareInputLocked(inputId, mUserId);
+ if (mUserId == mCurrentUserId) {
+ Slog.d(TAG,
+ "ServiceCallback: removeHardwareInput, inputId: " + inputId + " by "
+ + mComponent + ", userId: " + mUserId);
+ removeHardwareInputLocked(inputId, mUserId);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4578,6 +4619,11 @@ public final class TvInputManagerService extends SystemService {
private final class HardwareListener implements TvInputHardwareManager.Listener {
@Override
public void onStateChanged(String inputId, int state) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onStateChanged: inputId " + (inputId != null ? inputId : "null")
+ + ", state " + state);
+ }
synchronized (mLock) {
setStateLocked(inputId, state, mCurrentUserId);
}
@@ -4585,6 +4631,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHardwareDeviceAdded: TvInputHardwareInfo "
+ + (info != null ? info.toString() : "null"));
+ }
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
@@ -4607,6 +4658,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHardwareDeviceRemoved: TvInputHardwareInfo "
+ + (info != null ? info.toString() : "null"));
+ }
synchronized (mLock) {
String relatedInputId =
mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId());
@@ -4634,6 +4690,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceAdded: HdmiDeviceInfo "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
// Broadcast the event to all hardware inputs.
@@ -4656,6 +4717,11 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceRemoved: HdmiDeviceInfo "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
String relatedInputId =
mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId());
@@ -4683,6 +4749,12 @@ public final class TvInputManagerService extends SystemService {
@Override
public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onHdmiDeviceUpdated: inputId " + (inputId != null ? inputId : "null")
+ + ", deviceInfo: "
+ + (deviceInfo != null ? deviceInfo.toString() : "null"));
+ }
synchronized (mLock) {
Integer state;
switch (deviceInfo.getDevicePowerStatus()) {
diff --git a/services/core/java/com/android/server/uri/NeededUriGrants.java b/services/core/java/com/android/server/uri/NeededUriGrants.java
index 8c8f55304fbb..2fe61e00c97e 100644
--- a/services/core/java/com/android/server/uri/NeededUriGrants.java
+++ b/services/core/java/com/android/server/uri/NeededUriGrants.java
@@ -17,10 +17,13 @@
package com.android.server.uri;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.server.am.NeededUriGrantsProto;
+import java.util.Objects;
+
/** List of {@link GrantUri} a process needs. */
public class NeededUriGrants {
final String targetPkg;
@@ -35,6 +38,20 @@ public class NeededUriGrants {
this.uris = new ArraySet<>();
}
+ public void merge(NeededUriGrants other) {
+ if (other == null) return;
+ if (!Objects.equals(this.targetPkg, other.targetPkg)
+ || this.targetUid != other.targetUid || this.flags != other.flags) {
+ Slog.wtf("NeededUriGrants",
+ "The other NeededUriGrants does not share the same targetUid, targetPkg or "
+ + "flags. It cannot be merged into this NeededUriGrants. This "
+ + "NeededUriGrants: " + this.toStringWithoutUri()
+ + ". Other NeededUriGrants: " + other.toStringWithoutUri());
+ } else {
+ this.uris.addAll(other.uris);
+ }
+ }
+
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
@@ -47,4 +64,12 @@ public class NeededUriGrants {
}
proto.end(token);
}
+
+ public String toStringWithoutUri() {
+ return "NeededUriGrants{" +
+ "targetPkg='" + targetPkg + '\'' +
+ ", targetUid=" + targetUid +
+ ", flags=" + flags +
+ '}';
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ae30fcde39e1..d119a08b0c85 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -442,8 +442,6 @@ class ActivityClientController extends IActivityClientController.Stub {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- mService.mAmInternal.addCreatorToken(resultData);
-
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
@@ -502,6 +500,8 @@ class ActivityClientController extends IActivityClientController.Stub {
r.app.setLastActivityFinishTimeIfNeeded(SystemClock.uptimeMillis());
}
+ mService.mAmInternal.addCreatorToken(resultData, r.packageName);
+
final long origId = Binder.clearCallingIdentity();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
try {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 0580d4a5a4a3..c1f5a27b81e7 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
+import static com.android.server.wm.ActivityStarter.Request.DEFAULT_INTENT_CREATOR_UID;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -441,6 +442,17 @@ public class ActivityStartController {
0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
callingPid);
aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
+ int creatorUid = DEFAULT_INTENT_CREATOR_UID;
+ String creatorPackage = null;
+ if (ActivityManagerService.IntentCreatorToken.isValid(intent)) {
+ ActivityManagerService.IntentCreatorToken creatorToken =
+ (ActivityManagerService.IntentCreatorToken) intent.getCreatorToken();
+ if (creatorToken.getCreatorUid() != filterCallingUid) {
+ creatorUid = creatorToken.getCreatorUid();
+ creatorPackage = creatorToken.getCreatorPackage();
+ }
+ // leave creatorUid as -1 if the intent creator is the same as the launcher
+ }
if (aInfo != null) {
try {
@@ -454,6 +466,24 @@ public class ActivityStartController {
return START_CANCELED;
}
+ if (creatorUid != DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ NeededUriGrants creatorIntentGrants = mSupervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, creatorUid,
+ aInfo.applicationInfo.packageName,
+ UserHandle.getUserId(aInfo.applicationInfo.uid));
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ ActivityStarter.logForIntentRedirect(
+ "Creator URI Grant Caused Exception.", intent, creatorUid,
+ creatorPackage, filterCallingUid, callingPackage);
+ // TODO b/368559093 - rethrow the securityException.
+ }
+ }
if ((aInfo.applicationInfo.privateFlags
& ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
throw new IllegalArgumentException(
@@ -477,6 +507,8 @@ public class ActivityStartController {
.setCallingUid(callingUid)
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
+ .setIntentCreatorUid(creatorUid)
+ .setIntentCreatorPackage(creatorPackage)
.setRealCallingPid(realCallingPid)
.setRealCallingUid(realCallingUid)
.setActivityOptions(checkedOptions)
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5b5bb88cac98..5d3ae54f0934 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -132,6 +132,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.ProtoLog;
+import com.android.server.am.ActivityManagerService.IntentCreatorToken;
import com.android.server.am.PendingIntentRecord;
import com.android.server.pm.InstantAppResolver;
import com.android.server.pm.PackageArchiver;
@@ -384,6 +385,7 @@ class ActivityStarter {
private static final int DEFAULT_CALLING_PID = 0;
static final int DEFAULT_REAL_CALLING_UID = -1;
static final int DEFAULT_REAL_CALLING_PID = 0;
+ static final int DEFAULT_INTENT_CREATOR_UID = -1;
IApplicationThread caller;
Intent intent;
@@ -404,6 +406,8 @@ class ActivityStarter {
@Nullable String callingFeatureId;
int realCallingPid = DEFAULT_REAL_CALLING_PID;
int realCallingUid = DEFAULT_REAL_CALLING_UID;
+ int intentCreatorUid = DEFAULT_INTENT_CREATOR_UID;
+ String intentCreatorPackage;
int startFlags;
SafeActivityOptions activityOptions;
boolean ignoreTargetSecurity;
@@ -464,6 +468,8 @@ class ActivityStarter {
callingPid = DEFAULT_CALLING_PID;
callingUid = DEFAULT_CALLING_UID;
callingPackage = null;
+ intentCreatorUid = DEFAULT_INTENT_CREATOR_UID;
+ intentCreatorPackage = null;
callingFeatureId = null;
realCallingPid = DEFAULT_REAL_CALLING_PID;
realCallingUid = DEFAULT_REAL_CALLING_UID;
@@ -556,12 +562,14 @@ class ActivityStarter {
// "resolved" calling UID, where we try our best to identify the
// actual caller that is starting this activity
int resolvedCallingUid = callingUid;
+ String resolvedCallingPackage = callingPackage;
if (caller != null) {
synchronized (supervisor.mService.mGlobalLock) {
final WindowProcessController callerApp = supervisor.mService
.getProcessController(caller);
if (callerApp != null) {
resolvedCallingUid = callerApp.mInfo.uid;
+ resolvedCallingPackage = callerApp.mInfo.packageName;
}
}
}
@@ -597,7 +605,23 @@ class ActivityStarter {
// Collect information about the target of the Intent.
activityInfo = supervisor.resolveActivity(intent, resolveInfo, startFlags,
profilerInfo);
-
+ // Check if the Intent was redirected
+ if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
+ != 0) {
+ ActivityStarter.logForIntentRedirect(
+ "Unparceled intent does not have a creator token set.", intent,
+ intentCreatorUid,
+ intentCreatorPackage, resolvedCallingUid, resolvedCallingPackage);
+ // TODO b/368559093 - eventually ramp up to throw SecurityException
+ }
+ if (IntentCreatorToken.isValid(intent)) {
+ IntentCreatorToken creatorToken = (IntentCreatorToken) intent.getCreatorToken();
+ if (creatorToken.getCreatorUid() != resolvedCallingUid) {
+ intentCreatorUid = creatorToken.getCreatorUid();
+ intentCreatorPackage = creatorToken.getCreatorPackage();
+ }
+ // leave intentCreatorUid as -1 if the intent creator is the same as the launcher
+ }
// Carefully collect grants without holding lock
if (activityInfo != null) {
if (android.security.Flags.contentUriPermissionApis()) {
@@ -607,11 +631,52 @@ class ActivityStarter {
UserHandle.getUserId(activityInfo.applicationInfo.uid),
activityInfo.requireContentUriPermissionFromCaller,
/* requestHashCode */ this.hashCode());
+ if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, intentCreatorUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(activityInfo.applicationInfo.uid),
+ activityInfo.requireContentUriPermissionFromCaller,
+ /* requestHashCode */ this.hashCode());
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ ActivityStarter.logForIntentRedirect(
+ "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
+ intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage);
+ // TODO b/368559093 - rethrow the securityException.
+ }
+ }
} else {
intentGrants = supervisor.mService.mUgmInternal
.checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
activityInfo.applicationInfo.packageName,
UserHandle.getUserId(activityInfo.applicationInfo.uid));
+ if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID && intentGrants != null) {
+ try {
+ NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal
+ .checkGrantUriPermissionFromIntent(intent, intentCreatorUid,
+ activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(
+ activityInfo.applicationInfo.uid));
+ if (intentGrants == null) {
+ intentGrants = creatorIntentGrants;
+ } else {
+ intentGrants.merge(creatorIntentGrants);
+ }
+ } catch (SecurityException securityException) {
+ ActivityStarter.logForIntentRedirect(
+ "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
+ intentCreatorPackage, resolvedCallingUid,
+ resolvedCallingPackage);
+ // TODO b/368559093 - rethrow the securityException.
+ }
+ }
}
}
}
@@ -978,7 +1043,9 @@ class ActivityStarter {
int requestCode = request.requestCode;
int callingPid = request.callingPid;
int callingUid = request.callingUid;
- String callingPackage = request.callingPackage;
+ int intentCreatorUid = request.intentCreatorUid;
+ String intentCreatorPackage = request.intentCreatorPackage;
+ String intentCallingPackage = request.callingPackage;
String callingFeatureId = request.callingFeatureId;
final int realCallingPid = request.realCallingPid;
final int realCallingUid = request.realCallingUid;
@@ -1063,7 +1130,7 @@ class ActivityStarter {
// launched in the app flow to redirect to an activity picked by the user, where
// we want the final activity to consider it to have been launched by the
// previous app activity.
- callingPackage = sourceRecord.launchedFromPackage;
+ intentCallingPackage = sourceRecord.launchedFromPackage;
callingFeatureId = sourceRecord.launchedFromFeatureId;
}
}
@@ -1085,7 +1152,7 @@ class ActivityStarter {
if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
err = packageArchiver
.requestUnarchiveOnActivityStart(
- intent, callingPackage, mRequest.userId, realCallingUid);
+ intent, intentCallingPackage, mRequest.userId, realCallingUid);
}
}
}
@@ -1144,7 +1211,7 @@ class ActivityStarter {
boolean abort;
try {
abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
- requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
+ requestCode, callingPid, callingUid, intentCallingPackage, callingFeatureId,
request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
resultRootTask);
} catch (SecurityException e) {
@@ -1172,7 +1239,47 @@ class ActivityStarter {
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
- callingPackage);
+ intentCallingPackage);
+
+ if (intentCreatorUid != Request.DEFAULT_INTENT_CREATOR_UID) {
+ try {
+ if (!mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
+ requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
+ request.ignoreTargetSecurity, inTask != null, null, resultRecord,
+ resultRootTask)) {
+ logForIntentRedirect("Creator checkStartAnyActivityPermission Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid,
+ intentCallingPackage);
+ // TODO b/368559093 - set abort to true.
+ // abort = true;
+ }
+ } catch (SecurityException e) {
+ logForIntentRedirect("Creator checkStartAnyActivityPermission Caused Exception.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid,
+ intentCallingPackage);
+ // TODO b/368559093 - rethrow the exception.
+ //throw e;
+ }
+ if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid, 0,
+ resolvedType, aInfo.applicationInfo)) {
+ logForIntentRedirect("Creator IntentFirewall.checkStartActivity Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid,
+ intentCallingPackage);
+ // TODO b/368559093 - set abort to true.
+ // abort = true;
+ }
+
+ if (!mService.getPermissionPolicyInternal().checkStartActivity(intent, intentCreatorUid,
+ intentCreatorPackage)) {
+ logForIntentRedirect(
+ "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
+ intent, intentCreatorUid, intentCreatorPackage, callingUid,
+ intentCallingPackage);
+ // TODO b/368559093 - set abort to true.
+ // abort = true;
+ }
+ intent.removeCreatorTokenInfo();
+ }
// Merge the two options bundles, while realCallerOptions takes precedence.
ActivityOptions checkedOptions = options != null
@@ -1189,7 +1296,7 @@ class ActivityStarter {
balController.checkBackgroundActivityStart(
callingUid,
callingPid,
- callingPackage,
+ intentCallingPackage,
realCallingUid,
realCallingPid,
callerApp,
@@ -1210,7 +1317,7 @@ class ActivityStarter {
if (request.allowPendingRemoteAnimationRegistryLookup) {
checkedOptions = mService.getActivityStartController()
.getPendingRemoteAnimationRegistry()
- .overrideOptionsIfNeeded(callingPackage, checkedOptions);
+ .overrideOptionsIfNeeded(intentCallingPackage, checkedOptions);
}
if (mService.mController != null) {
try {
@@ -1226,7 +1333,8 @@ class ActivityStarter {
final TaskDisplayArea suggestedLaunchDisplayArea =
computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions);
- mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
+ mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags,
+ intentCallingPackage,
callingFeatureId);
if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) {
@@ -1264,7 +1372,8 @@ class ActivityStarter {
if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
aInfo.packageName, userId)) {
final IIntentSender target = mService.getIntentSenderLocked(
- ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingFeatureId,
+ ActivityManager.INTENT_SENDER_ACTIVITY, intentCallingPackage,
+ callingFeatureId,
callingUid, userId, null, null, 0, new Intent[]{intent},
new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT, null);
@@ -1327,7 +1436,8 @@ class ActivityStarter {
// app [on install success].
if (rInfo != null && rInfo.auxiliaryInfo != null) {
intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent,
- callingPackage, callingFeatureId, verificationBundle, resolvedType, userId);
+ intentCallingPackage, callingFeatureId, verificationBundle, resolvedType,
+ userId);
resolvedType = null;
callingUid = realCallingUid;
callingPid = realCallingPid;
@@ -1350,7 +1460,7 @@ class ActivityStarter {
.setCaller(callerApp)
.setLaunchedFromPid(callingPid)
.setLaunchedFromUid(callingUid)
- .setLaunchedFromPackage(callingPackage)
+ .setLaunchedFromPackage(intentCallingPackage)
.setLaunchedFromFeature(callingFeatureId)
.setIntent(intent)
.setResolvedType(resolvedType)
@@ -3308,6 +3418,16 @@ class ActivityStarter {
return this;
}
+ ActivityStarter setIntentCreatorUid(int uid) {
+ mRequest.intentCreatorUid = uid;
+ return this;
+ }
+
+ ActivityStarter setIntentCreatorPackage(String intentCreatorPackage) {
+ mRequest.intentCreatorPackage = intentCreatorPackage;
+ return this;
+ }
+
/**
* Sets the pid of the caller who requested to launch the activity.
*
@@ -3467,4 +3587,19 @@ class ActivityStarter {
pw.print(" mInTaskFragment=");
pw.println(mInTaskFragment);
}
+
+ static void logForIntentRedirect(String message, Intent intent, int intentCreatorUid,
+ String intentCreatorPackage, int callingUid, String callingPackage) {
+ String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
+ intentCreatorPackage, callingUid, callingPackage);
+ Slog.wtf(TAG, msg);
+ }
+
+ private static String getIntentRedirectPreventedLogMessage(String message, Intent intent,
+ int intentCreatorUid, String intentCreatorPackage, int callingUid,
+ String callingPackage) {
+ return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+ + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ + "; callingPackage: " + callingPackage + "; intent: " + intent;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4db478a13c92..5339753624d8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1228,7 +1228,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions) {
- mAmInternal.addCreatorToken(intent);
+ mAmInternal.addCreatorToken(intent, callingPackage);
return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
@@ -1243,7 +1243,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
enforceNotIsolatedCaller(reason);
if (intents != null) {
for (Intent intent : intents) {
- mAmInternal.addCreatorToken(intent);
+ mAmInternal.addCreatorToken(intent, callingPackage);
}
}
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason);
@@ -1275,7 +1275,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
- mAmInternal.addCreatorToken(intent);
+ mAmInternal.addCreatorToken(intent, callingPackage);
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
assertPackageMatchesCallingUid(callingPackage);
@@ -1330,7 +1330,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
// Remove existing mismatch flag so it can be properly updated later
fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
- mAmInternal.addCreatorToken(fillInIntent);
}
if (!(target instanceof PendingIntentRecord)) {
@@ -1339,6 +1338,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
PendingIntentRecord pir = (PendingIntentRecord) target;
+ if (fillInIntent != null) {
+ mAmInternal.addCreatorToken(fillInIntent, pir.getPackageName());
+ }
+
synchronized (mGlobalLock) {
// If this is coming from the currently resumed activity, it is
// effectively saying that app switches are allowed at this point.
@@ -1349,6 +1352,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mAppSwitchesState = APP_SWITCH_ALLOW;
}
}
+
return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null,
resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions);
}
@@ -1361,8 +1365,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- mAmInternal.addCreatorToken(intent);
-
SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions);
synchronized (mGlobalLock) {
@@ -1376,6 +1378,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
SafeActivityOptions.abort(options);
return false;
}
+
+ mAmInternal.addCreatorToken(intent, r.packageName);
+
intent = new Intent(intent);
// Remove existing mismatch flag so it can be properly updated later
intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 4d17ed24e734..eee4c86bc483 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -160,7 +160,7 @@ class AppTaskImpl extends IAppTask.Stub {
Intent intent, String resolvedType, Bundle bOptions) {
checkCallerOrSystemOrRoot();
mService.assertPackageMatchesCallingUid(callingPackage);
- mService.mAmInternal.addCreatorToken(intent);
+ mService.mAmInternal.addCreatorToken(intent, callingPackage);
int callingUser = UserHandle.getCallingUserId();
Task task;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 94cd2e64b057..70f9ebb0e61e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -34,6 +34,7 @@ import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY;
import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
+import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1237,8 +1238,9 @@ class BackNavigationController {
}
allWindowDrawn &= next.mAppWindowDrawn;
}
- // Do not remove until transition ready.
- if (!activity.isVisible()) {
+ // Do not remove windowless surfaces if the transaction has not been applied.
+ if (activity.getSyncTransactionCommitCallbackDepth() > 0
+ || activity.mSyncState != SYNC_STATE_NONE) {
return;
}
if (allWindowDrawn) {
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index a4fe0647ea79..9d21183c6c03 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -131,7 +131,9 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
final boolean changed = !com.android.window.flags.Flags.filterIrrelevantInputDeviceChange()
|| updateLastInputConfigurationSources();
- if (changed) {
+ // Even if the input devices are not changed, there could be other pending changes
+ // during booting. It's fine to apply earlier.
+ if (changed || !mService.mDisplayEnabled) {
synchronized (mService.mGlobalLock) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "inputDeviceConfigChanged");
mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 6067a9972bbe..1d4d6eb82c44 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -409,7 +409,7 @@ class InsetsSourceProvider {
}
final Point position = getWindowFrameSurfacePosition();
if (!mPosition.equals(position)) {
- mPosition.set(position.x, position.y);
+ mPosition.set(position);
if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
mHasPendingPosition = true;
@@ -553,6 +553,7 @@ class InsetsSourceProvider {
}
boolean initiallyVisible = mClientVisible;
final Point surfacePosition = getWindowFrameSurfacePosition();
+ mPosition.set(surfacePosition);
mAdapter = new ControlAdapter(surfacePosition);
if (mSource.getType() == WindowInsets.Type.ime()) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a4e4deb9ed7d..4861341f830a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5742,9 +5742,9 @@ class Task extends TaskFragment {
}
private boolean canMoveTaskToBack(Task task) {
- // Checks whether a task is a child of this task because it can be reparetned when
+ // Checks whether a task is a child of this task because it can be reparented when
// transition is deferred.
- if (task != this && task.getParent() != this) {
+ if (task != this && !task.isDescendantOf(this)) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 86adc1944371..1a107c24a16a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -133,7 +133,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private int mRapidActivityLaunchCount;
// all about the first app in the process
- final ApplicationInfo mInfo;
+ volatile ApplicationInfo mInfo;
final String mName;
final int mUid;
@@ -1805,12 +1805,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
overrideConfig.assetsSeq = assetSeq;
r.onRequestedOverrideConfigurationChanged(overrideConfig);
+ r.updateApplicationInfo(mInfo);
if (r.isVisibleRequested()) {
r.ensureActivityConfiguration();
}
}
}
+ public void updateApplicationInfo(ApplicationInfo aInfo) {
+ mInfo = aInfo;
+ }
+
/**
* This is called for sending {@link android.app.servertransaction.LaunchActivityItem}.
* The caller must call {@link #setLastReportedConfiguration} if the delivered configuration
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b9727f9f3970..4103c477a8ae 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -327,8 +327,6 @@ public final class SystemServer implements Dumpable {
* Implementation class names for services in the {@code SYSTEMSERVERCLASSPATH}
* from {@code PRODUCT_SYSTEM_SERVER_JARS} that are *not* in {@code services.jar}.
*/
- private static final String ARC_NETWORK_SERVICE_CLASS =
- "com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
@@ -2101,24 +2099,13 @@ public final class SystemServer implements Dumpable {
if (context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI)) {
// Wifi Service must be started first for wifi-related services.
- if (!isArc) {
- t.traceBegin("StartWifi");
- mSystemServiceManager.startServiceFromJar(
- WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
- t.traceEnd();
- t.traceBegin("StartWifiScanning");
- mSystemServiceManager.startServiceFromJar(
- WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
- t.traceEnd();
- }
- }
-
- // ARC - ArcNetworkService registers the ARC network stack and replaces the
- // stock WiFi service in both ARC++ container and ARCVM. Always starts the ARC network
- // stack regardless of whether FEATURE_WIFI is enabled/disabled (b/254755875).
- if (isArc) {
- t.traceBegin("StartArcNetworking");
- mSystemServiceManager.startService(ARC_NETWORK_SERVICE_CLASS);
+ t.traceBegin("StartWifi");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ t.traceBegin("StartWifiScanning");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
t.traceEnd();
}
diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
index ff901af3defa..30df4c821134 100644
--- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java
@@ -96,6 +96,8 @@ class CallLogQueryHelper {
} catch (SecurityException ex) {
Slog.e(TAG, "Query call log failed: " + ex);
return false;
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying call log.", e);
}
return hasResults;
}
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 2505abf2d160..2bd9d87b0124 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -151,9 +151,11 @@ class ContactsQueryHelper {
found = true;
}
} catch (SQLiteException exception) {
- Slog.w("SQLite exception when querying contacts.", exception);
+ Slog.w(TAG, "SQLite exception when querying contacts.", exception);
} catch (IllegalArgumentException exception) {
- Slog.w("Illegal Argument exception when querying contacts.", exception);
+ Slog.w(TAG, "Illegal Argument exception when querying contacts.", exception);
+ } catch (Exception exception) {
+ Slog.e(TAG, "Exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
@@ -181,6 +183,8 @@ class ContactsQueryHelper {
mPhoneNumber = cursor.getString(phoneNumIdx);
}
}
+ } catch (Exception exception) {
+ Slog.e(TAG, "Exception when querying contact phone number.", exception);
}
return true;
}
diff --git a/services/people/java/com/android/server/people/data/MmsQueryHelper.java b/services/people/java/com/android/server/people/data/MmsQueryHelper.java
index 39dba9c73ba2..414a523fb186 100644
--- a/services/people/java/com/android/server/people/data/MmsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/MmsQueryHelper.java
@@ -100,6 +100,8 @@ class MmsQueryHelper {
}
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying MMS table.", e);
} finally {
Binder.defaultBlockingForCurrentThread();
}
@@ -133,6 +135,8 @@ class MmsQueryHelper {
address = cursor.getString(addrIndex);
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying MMS address table.", e);
}
if (!Mms.isPhoneNumber(address)) {
return null;
diff --git a/services/people/java/com/android/server/people/data/SmsQueryHelper.java b/services/people/java/com/android/server/people/data/SmsQueryHelper.java
index a5eb3a581616..f8ff3abc8e4c 100644
--- a/services/people/java/com/android/server/people/data/SmsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/SmsQueryHelper.java
@@ -98,6 +98,8 @@ class SmsQueryHelper {
}
}
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception when querying SMS table.", e);
} finally {
Binder.defaultBlockingForCurrentThread();
}
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 cbca434a6bb6..8af5b2081b81 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
@@ -21,6 +21,7 @@ import android.content.pm.PackageInstaller.SessionParams
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
+import android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
import android.content.pm.PackageManager
import android.content.pm.verify.domain.DomainSet
import android.os.Parcel
@@ -33,6 +34,11 @@ import com.android.internal.os.BackgroundThread
import com.android.server.pm.verify.pkg.VerifierController
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
import libcore.io.IoUtils
import org.junit.Before
import org.junit.Rule
@@ -46,11 +52,6 @@ import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
-import java.io.File
-import java.io.FileInputStream
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
@Presubmit
class PackageInstallerSessionTest {
@@ -197,7 +198,8 @@ class PackageInstallerSessionTest {
/* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
/* stagedSessionErrorMessage */ "some error",
/* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
- /* VerifierController */ mock(VerifierController::class.java)
+ /* VerifierController */ mock(VerifierController::class.java),
+ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
)
}
@@ -339,6 +341,7 @@ class PackageInstallerSessionTest {
assertThat(expected.childSessionIds).asList()
.containsExactlyElementsIn(actual.childSessionIds.toList())
assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
+ assertThat(expected.verificationPolicy).isEqualTo(actual.verificationPolicy)
}
private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
index be094b0152bc..37b23b107ecd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
@@ -16,6 +16,9 @@
package com.android.server.pm.verify.pkg;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
+import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_OPEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -92,6 +95,9 @@ public class VerifierControllerTest {
private static final long TEST_TIMEOUT_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(1);
private static final long TEST_MAX_TIMEOUT_DURATION_MILLIS =
TimeUnit.MINUTES.toMillis(10);
+ private static final long TEST_VERIFIER_CONNECTION_TIMEOUT_DURATION_MILLIS =
+ TimeUnit.SECONDS.toMillis(10);
+ private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED;
private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>();
private final PersistableBundle mTestExtensionParams = new PersistableBundle();
@@ -124,6 +130,9 @@ public class VerifierControllerTest {
TEST_TIMEOUT_DURATION_MILLIS);
when(mInjector.getMaxVerificationExtendedTimeoutMillis()).thenReturn(
TEST_MAX_TIMEOUT_DURATION_MILLIS);
+ when(mInjector.getVerifierConnectionTimeoutMillis()).thenReturn(
+ TEST_VERIFIER_CONNECTION_TIMEOUT_DURATION_MILLIS
+ );
// Mock time forward as the code continues to check for the current time
when(mInjector.getCurrentTimeMillis())
.thenReturn(TEST_REQUEST_START_TIME)
@@ -159,12 +168,12 @@ public class VerifierControllerTest {
.isFalse();
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isFalse();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isFalse();
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ true)).isFalse();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ true)).isFalse();
verifyZeroInteractions(mSessionCallback);
}
@@ -200,8 +209,8 @@ public class VerifierControllerTest {
ServiceConnector.ServiceLifecycleCallbacks<IVerifierService> callbacks = captor.getValue();
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService, times(1)).onVerificationRequired(any(VerificationSession.class));
callbacks.onBinderDied();
// Test that nothing crashes if the service connection is lost
@@ -212,12 +221,12 @@ public class VerifierControllerTest {
verifyNoMoreInteractions(mMockService);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ true)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ true)).isTrue();
mVerifierController.notifyVerificationTimeout(TEST_ID);
verify(mMockService, times(1)).onVerificationTimeout(eq(TEST_ID));
}
@@ -226,8 +235,8 @@ public class VerifierControllerTest {
public void testNotifyPackageNameAvailable() throws Exception {
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME);
verify(mMockService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME));
}
@@ -236,8 +245,8 @@ public class VerifierControllerTest {
public void testNotifyVerificationCancelled() throws Exception {
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME);
verify(mMockService).onVerificationCancelled(eq(TEST_PACKAGE_NAME));
}
@@ -248,8 +257,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
assertThat(session.getId()).isEqualTo(TEST_ID);
@@ -276,8 +285,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ true)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ true)).isTrue();
verify(mMockService).onVerificationRetry(captor.capture());
VerificationSession session = captor.getValue();
assertThat(session.getId()).isEqualTo(TEST_ID);
@@ -302,8 +311,8 @@ public class VerifierControllerTest {
public void testNotifyVerificationTimeout() throws Exception {
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ true)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ true)).isTrue();
mVerifierController.notifyVerificationTimeout(TEST_ID);
verify(mMockService).onVerificationTimeout(eq(TEST_ID));
}
@@ -320,8 +329,8 @@ public class VerifierControllerTest {
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
verify(mSessionCallback, times(1)).onTimeout();
verify(mInjector, times(2)).getCurrentTimeMillis();
@@ -339,8 +348,8 @@ public class VerifierControllerTest {
.thenAnswer(i -> true);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
verify(mSessionCallback, times(1)).onTimeout();
verify(mInjector, times(2)).getCurrentTimeMillis();
@@ -350,8 +359,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ true)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ true)).isTrue();
verify(mMockService).onVerificationRetry(captor.capture());
VerificationSession session = captor.getValue();
VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
@@ -367,8 +376,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
session.reportVerificationIncomplete(VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN);
@@ -383,8 +392,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
@@ -401,8 +410,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
VerificationStatus status = new VerificationStatus.Builder()
@@ -421,8 +430,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
assertThat(mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false)).isTrue();
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false)).isTrue();
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build();
@@ -439,8 +448,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false);
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false);
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS;
@@ -456,8 +465,8 @@ public class VerifierControllerTest {
ArgumentCaptor.forClass(VerificationSession.class);
mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false);
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false);
verify(mMockService).onVerificationRequired(captor.capture());
VerificationSession session = captor.getValue();
final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS;
@@ -493,10 +502,27 @@ public class VerifierControllerTest {
.thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS + 1);
mVerifierController.startVerificationSession(
mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
- TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback,
- /* retry= */ false);
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false);
verify(mHandler, times(3)).sendMessageAtTime(any(Message.class), anyLong());
verify(mInjector, times(6)).getCurrentTimeMillis();
verify(mSessionCallback, times(1)).onTimeout();
}
+
+ @Test
+ public void testPolicyOverride() throws Exception {
+ ArgumentCaptor<VerificationSession> captor =
+ ArgumentCaptor.forClass(VerificationSession.class);
+ mVerifierController.startVerificationSession(
+ mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI,
+ TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams,
+ mSessionCallback, /* retry= */ false);
+ verify(mMockService).onVerificationRequired(captor.capture());
+ VerificationSession session = captor.getValue();
+ final int policy = VERIFICATION_POLICY_BLOCK_FAIL_OPEN;
+ when(mSessionCallback.setVerificationPolicy(eq(policy))).thenReturn(true);
+ assertThat(session.setVerificationPolicy(policy)).isTrue();
+ assertThat(session.getVerificationPolicy()).isEqualTo(policy);
+ verify(mSessionCallback, times(1)).setVerificationPolicy(eq(policy));
+ }
}
diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp
index c841643c6654..836f90b992d6 100644
--- a/services/tests/appfunctions/Android.bp
+++ b/services/tests/appfunctions/Android.bp
@@ -36,7 +36,9 @@ android_test {
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.truth",
+ "androidx.core_core-ktx",
"kotlin-test",
+ "kotlinx_coroutines_test",
"platform-test-annotations",
"services.appfunctions",
"servicestests-core-utils",
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt
new file mode 100644
index 000000000000..a69e9025bfa0
--- /dev/null
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appfunctions
+
+import android.app.appfunctions.flags.Flags
+import android.content.Context
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@RequiresFlagsEnabled(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+class AppFunctionManagerServiceImplTest {
+ @get:Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val context: Context
+ get() = ApplicationProvider.getApplicationContext()
+
+ private val serviceImpl = AppFunctionManagerServiceImpl(context)
+
+ @Test
+ fun testGetLockForPackage_samePackage() {
+ val packageName = "com.example.app"
+ val lock1 = serviceImpl.getLockForPackage(packageName)
+ val lock2 = serviceImpl.getLockForPackage(packageName)
+
+ // Assert that the same lock object is returned for the same package name
+ assertThat(lock1).isEqualTo(lock2)
+ }
+
+ @Test
+ fun testGetLockForPackage_differentPackages() {
+ val packageName1 = "com.example.app1"
+ val packageName2 = "com.example.app2"
+ val lock1 = serviceImpl.getLockForPackage(packageName1)
+ val lock2 = serviceImpl.getLockForPackage(packageName2)
+
+ // Assert that different lock objects are returned for different package names
+ assertThat(lock1).isNotEqualTo(lock2)
+ }
+
+ @Ignore("Hard to deterministically trigger the garbage collector.")
+ @Test
+ fun testWeakReference_garbageCollected_differentLockAfterGC() = runTest {
+ // Create a large number of temporary objects to put pressure on the GC
+ val tempObjects = MutableList<Any?>(10000000) { Any() }
+ var callingPackage: String? = "com.example.app"
+ var lock1: Any? = serviceImpl.getLockForPackage(callingPackage)
+ callingPackage = null // Set the key to null
+ val lock1Hash = lock1.hashCode()
+ lock1 = null
+
+ // Create memory pressure
+ repeat(3) {
+ for (i in 1..100) {
+ "a".repeat(10000)
+ }
+ System.gc() // Suggest garbage collection
+ System.runFinalization()
+ }
+ // Get the lock again - it should be a different object now
+ val lock2 = serviceImpl.getLockForPackage("com.example.app")
+ // Assert that the lock objects are different
+ assertThat(lock1Hash).isNotEqualTo(lock2.hashCode())
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 6ccc03709b4f..2a825f35bf62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -107,6 +107,7 @@ import android.os.IBinder;
import android.os.IProgressListener;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -1309,12 +1310,13 @@ public class ActivityManagerServiceTest {
intent.putExtra("EXTRA_INTENT0", extraIntent);
intent.collectExtraIntentKeys();
- mAms.addCreatorToken(intent);
+ mAms.addCreatorToken(intent, TEST_PACKAGE);
ActivityManagerService.IntentCreatorToken token =
(ActivityManagerService.IntentCreatorToken) extraIntent.getCreatorToken();
assertThat(token).isNotNull();
assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
}
@Test
@@ -1330,7 +1332,7 @@ public class ActivityManagerServiceTest {
fillinIntent.collectExtraIntentKeys();
intent.fillIn(fillinIntent, FILL_IN_ACTION);
- mAms.addCreatorToken(fillinIntent);
+ mAms.addCreatorToken(fillinIntent, TEST_PACKAGE);
fillinExtraIntent = intent.getParcelableExtra("FILLIN_EXTRA_INTENT0", Intent.class);
@@ -1338,6 +1340,49 @@ public class ActivityManagerServiceTest {
(ActivityManagerService.IntentCreatorToken) fillinExtraIntent.getCreatorToken();
assertThat(token).isNotNull();
assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testCheckCreatorToken() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT", extraIntent);
+
+ intent.collectExtraIntentKeys();
+
+ // mimic client hack and sneak in an extra intent without going thru collectExtraIntentKeys.
+ Intent extraIntent2 = new Intent("EXTRA_INTENT_ACTION2");
+ intent.putExtra("EXTRA_INTENT2", extraIntent2);
+
+ // mock parceling on the client side, unparcling on the system server side, then
+ // addCreatorToken on system server side.
+ final Parcel parcel = Parcel.obtain();
+ intent.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Intent newIntent = new Intent();
+ newIntent.readFromParcel(parcel);
+ intent = newIntent;
+ mAms.addCreatorToken(intent, TEST_PACKAGE);
+ // entering the target app's process.
+ intent.checkCreatorToken();
+
+ Intent extraIntent3 = new Intent("EXTRA_INTENT_ACTION3");
+ intent.putExtra("EXTRA_INTENT3", extraIntent3);
+
+ extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
+ extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
+ extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
+
+ assertThat(extraIntent.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+ // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
+ assertThat(extraIntent2.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);
+ // local created intent should not have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
+ assertThat(extraIntent3.getExtendedFlags()
+ & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
}
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9781851da7e6..5c718d982476 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
-import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -77,9 +76,12 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -90,6 +92,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
@@ -131,6 +134,7 @@ public class QuotaControllerTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private QuotaController mQuotaController;
private QuotaController.QcConstants mQcConstants;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
@@ -930,7 +934,8 @@ public class QuotaControllerTest {
* Tests that getExecutionStatsLocked returns the correct stats.
*/
@Test
- public void testGetExecutionStatsLocked_Values() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - HOUR_IN_MILLIS),
@@ -1015,11 +1020,112 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED;
+ expectedStats.expirationTimeElapsed = now + 14 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ // There is only one session in the past active bucket window, the empty time for this
+ // window is the bucket window size - duration of the session.
+ expectedStats.expirationTimeElapsed = now + 24 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+
+ // Working
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 10;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 2;
+ expectedStats.inQuotaTimeElapsed = now + 2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
+ }
+
+ // Frequent
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 3;
+ expectedStats.inQuotaTimeElapsed = now + 10 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX));
+ }
+
+ // Rare
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 20;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.sessionCountInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
+ + mQcConstants.IN_QUOTA_BUFFER_MS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
*/
@Test
- public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_LegacyDefaultBucketWindowSizes() {
// Set time to 3 minutes after boot.
advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
advanceElapsedClock(3 * MINUTE_IN_MILLIS);
@@ -1042,7 +1148,8 @@ public class QuotaControllerTest {
expectedStats.sessionCountInWindow = 1;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
}
// Working
@@ -1052,7 +1159,8 @@ public class QuotaControllerTest {
expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
}
// Frequent
@@ -1073,7 +1181,83 @@ public class QuotaControllerTest {
expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
synchronized (mQuotaController.mLock) {
assertEquals(expectedStats,
- mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() {
+ // Set time to 3 minutes after boot.
+ advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+ advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Exempted
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 2;
+ expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 2;
+ expectedStats.sessionCountInWindow = 1;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ EXEMPTED_INDEX));
+ }
+
+ // Active
+ expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS;
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+ expectedStats.expirationTimeElapsed = 20 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ ACTIVE_INDEX));
+ }
+
+ // Working
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+ expectedStats.expirationTimeElapsed = 4 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ WORKING_INDEX));
+ }
+
+ // Frequent
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+ expectedStats.expirationTimeElapsed = 12 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX));
+ }
+
+ // Rare
+ expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS;
+ expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+ expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+ expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ synchronized (mQuotaController.mLock) {
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test",
+ RARE_INDEX));
}
}
@@ -1081,7 +1265,8 @@ public class QuotaControllerTest {
* Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
*/
@Test
- public void testGetExecutionStatsLocked_CoalescingSessions() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_CoalescingSessions_LegacyDefaultBucketWindowSizes() {
for (int i = 0; i < 10; ++i) {
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
@@ -1098,12 +1283,14 @@ public class QuotaControllerTest {
advanceElapsedClock(54 * SECOND_IN_MILLIS);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
- JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 500, 1), false);
advanceElapsedClock(500);
advanceElapsedClock(400);
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(
- JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 100, 1), false);
advanceElapsedClock(100);
advanceElapsedClock(5 * SECOND_IN_MILLIS);
}
@@ -1229,6 +1416,164 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() {
+ for (int i = 0; i < 20; ++i) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ 5 * MINUTE_IN_MILLIS, 5), false);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ for (int j = 0; j < 5; ++j) {
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(),
+ MINUTE_IN_MILLIS, 2), false);
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ advanceElapsedClock(54 * SECOND_IN_MILLIS);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
+ advanceElapsedClock(500);
+ advanceElapsedClock(400);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
+ advanceElapsedClock(100);
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ }
+ advanceElapsedClock(40 * MINUTE_IN_MILLIS);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(64, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(192, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(320, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(44, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(132, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(220, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * SECOND_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced
+ assertEquals(28, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced
+ assertEquals(84, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced
+ assertEquals(140, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ MINUTE_IN_MILLIS);
+
+ // Only two TimingSessions there for every hour.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(8, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(24, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(40, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 5 * MINUTE_IN_MILLIS);
+
+ // Only one TimingSessions there for every hour
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
+ 15 * MINUTE_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+
+ // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
+ // between an hour and 15 minutes.
+ setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.invalidateAllExecutionStatsLocked();
+ assertEquals(0, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
+ assertEquals(4, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
+ assertEquals(12, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
+ assertEquals(20, mQuotaController.getExecutionStatsLocked(
+ 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
+ }
+ }
+
/**
* Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
*/
@@ -1454,7 +1799,8 @@ public class QuotaControllerTest {
}
@Test
- public void testGetMaxJobExecutionTimeLocked_EJ() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetMaxJobExecutionTimeLocked_EJ_LegacyDefaultEJLimits() {
final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
@@ -1530,11 +1876,91 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetMaxJobExecutionTimeLocked_EJ_NewDefaultEJLimits() {
+ final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
+ timeUsedMs, 5), true);
+ JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
+ setStandbyBucket(RARE_INDEX, job);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ }
+
+ setCharging();
+ synchronized (mQuotaController.mLock) {
+ assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ setDischarging();
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ // Test used quota rolling out of window.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
+ }
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
+ timeUsedMs, 5), true);
+
+ setProcessState(getProcessStateQuotaFreeThreshold());
+ synchronized (mQuotaController.mLock) {
+ // max of 50% WORKING limit and remaining quota
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+
+ // Top-started job
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ mQuotaController.maybeStopTrackingJobLocked(job, null);
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
+ mQuotaController.getMaxJobExecutionTimeMsLocked(job));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
@@ -1558,27 +1984,56 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
+ false);
+
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
+ // window size = allowed time, so jobs can essentially run non-stop until they reach the
+ // max execution time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Close to RARE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS),
30 * SECOND_IN_MILLIS, 5), false);
// Far away from FREQUENT boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
// Overlap WORKING_SET boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS),
3 * MINUTE_IN_MILLIS, 5), false);
// Close to ACTIVE boundary.
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
setStandbyBucket(RARE_INDEX);
synchronized (mQuotaController.mLock) {
@@ -1623,6 +2078,69 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to RARE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS),
+ 30 * SECOND_IN_MILLIS, 5), false);
+ // Far away from FREQUENT boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+ // Overlap WORKING_SET boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS, 5), false);
+
+ setStandbyBucket(RARE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ setStandbyBucket(FREQUENT_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ setStandbyBucket(WORKING_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(5 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ // ACTIVE window != allowed time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(10 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
*/
@@ -1747,7 +2265,8 @@ public class QuotaControllerTest {
* Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (24 * HOUR_IN_MILLIS),
@@ -1773,55 +2292,84 @@ public class QuotaControllerTest {
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS),
+ mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5),
+ false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5),
+ false);
+
+ setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
+ 20 * MINUTE_IN_MILLIS);
+ setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS);
+ // window size != allowed time.
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
/**
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
* window and the session is rolling out of the window.
*/
@Test
- public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() {
+ @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_LegacyDefaultBucketWindowSizes() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (24 * HOUR_IN_MILLIS),
- 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false);
setStandbyBucket(RARE_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false);
setStandbyBucket(FREQUENT_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (2 * HOUR_IN_MILLIS),
- 10 * MINUTE_IN_MILLIS, 5), false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false);
setStandbyBucket(WORKING_INDEX);
synchronized (mQuotaController.mLock) {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(10 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
- createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
- false);
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5), false);
// ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
// max execution time.
setStandbyBucket(ACTIVE_INDEX);
@@ -1829,7 +2377,85 @@ public class QuotaControllerTest {
assertEquals(0,
mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
- assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS
+ - (mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS
+ + mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS
+ + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS),
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS)
+ public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_NewDefaultBucketWindowSizes() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false);
+ setStandbyBucket(RARE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false);
+ setStandbyBucket(FREQUENT_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false);
+ setStandbyBucket(WORKING_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5),
+ false);
+ // ACTIVE window != allowed time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - mQcConstants.WINDOW_SIZE_EXEMPTED_MS,
+ mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 5),
+ false);
+ // EXEMPTED window != allowed time
+ setStandbyBucket(EXEMPTED_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(0,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
}
@@ -3733,7 +4359,7 @@ public class QuotaControllerTest {
/** Tests that Timers count FOREGROUND_SERVICE jobs. */
@Test
- @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
public void testTimerTracking_Fgs() {
setDischarging();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index e131a98b52d0..de029e0d770f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -185,7 +185,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable(),
+ nullable())
distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
arrayOfNulls(0), TEST_USER_ID)
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 43a8aa957fa5..124c41ed449f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -741,7 +741,8 @@ public class StagingManagerTest {
/* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
/* stagedSessionErrorMessage */ "no error",
/* preVerifiedDomains */ null,
- /* verifierController */ null);
+ /* verifierController */ null,
+ /* verificationPolicy */ PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED);
StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/security/forensic/Android.bp b/services/tests/security/forensic/Android.bp
new file mode 100644
index 000000000000..adc49040a004
--- /dev/null
+++ b/services/tests/security/forensic/Android.bp
@@ -0,0 +1,41 @@
+package {
+ default_team: "trendy_team_platform_security",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "ForensicServiceTests",
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "compatibility-device-util-axt",
+ "frameworks-base-testutils",
+ "junit",
+ "platform-test-annotations",
+ "services.core",
+ "truth",
+ ],
+
+ platform_apis: true,
+
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+ dxflags: ["--multi-dex"],
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/security/forensic/AndroidManifest.xml b/services/tests/security/forensic/AndroidManifest.xml
new file mode 100644
index 000000000000..40feb19aecba
--- /dev/null
+++ b/services/tests/security/forensic/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.security.forensic.tests">
+
+ <application android:testOnly="true">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.security.forensic.tests"
+ android:label="Frameworks Forensic Services Tests"/>
+</manifest>
diff --git a/services/tests/security/forensic/AndroidTest.xml b/services/tests/security/forensic/AndroidTest.xml
new file mode 100644
index 000000000000..bbe2e9c303ce
--- /dev/null
+++ b/services/tests/security/forensic/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<configuration description="Runs Frameworks Forensic Service tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true"/>
+ <option name="test-file-name" value="ForensicServiceTests.apk"/>
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <option name="test-tag" value="ForensicServiceTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.server.security.forensic.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/security/forensic/TEST_MAPPING b/services/tests/security/forensic/TEST_MAPPING
new file mode 100644
index 000000000000..bd8b2ab7c41f
--- /dev/null
+++ b/services/tests/security/forensic/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "ForensicServiceTests"
+ }
+ ]
+}
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
new file mode 100644
index 000000000000..7aa2e0f609a7
--- /dev/null
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -0,0 +1,387 @@
+/*
+ * 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.security.forensic;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.security.forensic.IForensicServiceCommandCallback;
+import android.security.forensic.IForensicServiceStateCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ForensicServiceTest {
+ private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
+ private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
+ private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE;
+ private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED;
+
+ private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN;
+ private static final int ERROR_PERMISSION_DENIED =
+ IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED;
+ private static final int ERROR_INVALID_STATE_TRANSITION =
+ IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION;
+ private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE;
+ private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
+ IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+
+ @Mock
+ private Context mContextSpy;
+
+ private ForensicService mForensicService;
+ private TestLooper mTestLooper;
+ private Looper mLooper;
+
+ @SuppressLint("VisibleForTests")
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mLooper = mTestLooper.getLooper();
+ mForensicService = new ForensicService(new MockInjector(mContextSpy));
+ mForensicService.onStart();
+ }
+
+ @Test
+ public void testMonitorState_Invisible() throws RemoteException {
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ mForensicService.getBinderService().monitorState(scb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb.mState);
+ }
+
+ @Test
+ public void testMonitorState_Invisible_TwoMonitors() throws RemoteException {
+ StateCallback scb1 = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb1.mState);
+ mForensicService.getBinderService().monitorState(scb1);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+
+ StateCallback scb2 = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb2.mState);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ }
+
+ @Test
+ public void testMakeVisible_FromInvisible() throws RemoteException {
+ StateCallback scb = new StateCallback();
+ assertEquals(STATE_UNKNOWN, scb.mState);
+ mForensicService.getBinderService().monitorState(scb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testMakeVisible_FromInvisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_VISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testMakeVisible_FromEnabled_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeVisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
+ }
+
+ @Test
+ public void testMakeInvisible_FromInvisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeInvisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testMakeInvisible_FromVisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_VISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeInvisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testMakeInvisible_FromEnabled_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().makeInvisible(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+
+ @Test
+ public void testEnable_FromInvisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
+ }
+
+ @Test
+ public void testEnable_FromVisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_VISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testEnable_FromEnabled_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().enable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testDisable_FromInvisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_INVISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().disable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_INVISIBLE, scb1.mState);
+ assertEquals(STATE_INVISIBLE, scb2.mState);
+ assertNotNull(ccb.mErrorCode);
+ assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue());
+ }
+
+ @Test
+ public void testDisable_FromVisible_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_VISIBLE);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().disable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ @Test
+ public void testDisable_FromEnabled_TwoMonitors() throws RemoteException {
+ mForensicService.setState(STATE_ENABLED);
+ StateCallback scb1 = new StateCallback();
+ StateCallback scb2 = new StateCallback();
+ mForensicService.getBinderService().monitorState(scb1);
+ mForensicService.getBinderService().monitorState(scb2);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_ENABLED, scb1.mState);
+ assertEquals(STATE_ENABLED, scb2.mState);
+
+ CommandCallback ccb = new CommandCallback();
+ mForensicService.getBinderService().disable(ccb);
+ mTestLooper.dispatchAll();
+ assertEquals(STATE_VISIBLE, scb1.mState);
+ assertEquals(STATE_VISIBLE, scb2.mState);
+ assertNull(ccb.mErrorCode);
+ }
+
+ private class MockInjector implements ForensicService.Injector {
+ private final Context mContext;
+
+ MockInjector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+
+ @Override
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ }
+
+ private static class StateCallback extends IForensicServiceStateCallback.Stub {
+ int mState = STATE_UNKNOWN;
+
+ @Override
+ public void onStateChange(int state) throws RemoteException {
+ mState = state;
+ }
+ }
+
+ private static class CommandCallback extends IForensicServiceCommandCallback.Stub {
+ Integer mErrorCode = null;
+
+ public void reset() {
+ mErrorCode = null;
+ }
+
+ @Override
+ public void onSuccess() throws RemoteException {
+
+ }
+
+ @Override
+ public void onFailure(int errorCode) throws RemoteException {
+ mErrorCode = errorCode;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index b745e6a7d4a5..e5831b326de5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1417,7 +1417,7 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
}
@@ -1471,55 +1471,55 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseDownEventsDoNotMoveMagnifierViewport() {
runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusDownEventsDoNotMoveMagnifierViewport() {
runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseUpEventsDoNotMoveMagnifierViewport() {
runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testStylusUpEventsDoNotMoveMagnifierViewport() {
runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void testMouseMoveEventsMoveMagnifierViewport() {
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index d80a1f056e94..45c157d1c1a0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -93,7 +93,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
@@ -108,7 +108,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
@@ -123,7 +123,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
@@ -138,7 +138,7 @@ public class MagnificationGestureHandlerTest {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX)
public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index d1f6c2f9f1f0..9c6412b81b34 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,10 +67,8 @@ import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
-import com.android.internal.pm.parsing.PackageParser2;
import com.android.server.compat.PlatformCompat;
import com.android.server.integrity.model.IntegrityCheckResult;
-import com.android.server.pm.parsing.TestPackageParser2;
import com.android.server.testutils.TestUtils;
import org.junit.After;
@@ -140,8 +138,6 @@ public class AppIntegrityManagerServiceImplTest {
@Mock IntegrityFileManager mIntegrityFileManager;
@Mock Handler mHandler;
- private Supplier<PackageParser2> mParserSupplier = TestPackageParser2::new;
-
private final Context mRealContext = InstrumentationRegistry.getTargetContext();
private PackageManager mSpyPackageManager;
@@ -173,7 +169,6 @@ public class AppIntegrityManagerServiceImplTest {
new AppIntegrityManagerServiceImpl(
mMockContext,
mPackageManagerInternal,
- mParserSupplier,
mIntegrityFileManager,
mHandler);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 7e22d74c64e1..b1d658cb1e86 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -25,6 +25,7 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_TASK;
import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
+import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.ContentRecordingSession.TARGET_UID_FULL_SCREEN;
import static android.view.ContentRecordingSession.TARGET_UID_UNKNOWN;
import static android.view.ContentRecordingSession.createDisplaySession;
@@ -80,6 +81,7 @@ import android.os.test.TestLooper;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.testing.TestableContext;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
@@ -372,6 +374,50 @@ public class MediaProjectionManagerServiceTest {
});
}
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_screenshareProtectionsDisabled()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ int value = Settings.Global.getInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0);
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1);
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ } finally {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, value);
+ }
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags
+ .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS)
+ @Test
+ public void testCreateProjection_keyguardLocked_noDisplayCreated()
+ throws NameNotFoundException {
+ MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ doReturn(true).when(mKeyguardManager).isKeyguardLocked();
+
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ RECORD_SENSITIVE_CONTENT, projection.packageName);
+
+ projection.start(mIMediaProjectionCallback);
+
+ // The projection was started because it was allowed to capture the keyguard.
+ assertThat(mService.getActiveProjectionInfo()).isNotNull();
+ }
+
@Test
public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
throws NameNotFoundException {
@@ -485,6 +531,7 @@ public class MediaProjectionManagerServiceTest {
MediaProjectionManagerService.MediaProjection projection =
startProjectionPreconditions(service);
projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
assertThat(service.getActiveProjectionInfo()).isNotNull();
@@ -507,6 +554,7 @@ public class MediaProjectionManagerServiceTest {
MediaProjectionManagerService.MediaProjection projection =
startProjectionPreconditions(service);
projection.start(mIMediaProjectionCallback);
+ projection.notifyVirtualDisplayCreated(10);
assertThat(service.getActiveProjectionInfo()).isNotNull();
diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
index a54501029712..f45eddcf4480 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.test.mock.MockContentProvider;
@@ -58,6 +59,7 @@ public final class CallLogQueryHelperTest {
private MatrixCursor mCursor;
private EventConsumer mEventConsumer;
private CallLogQueryHelper mHelper;
+ private CallLogContentProvider mCallLogContentProvider;
@Before
public void setUp() {
@@ -66,7 +68,8 @@ public final class CallLogQueryHelperTest {
mCursor = new MatrixCursor(CALL_LOG_COLUMNS);
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(CALL_LOG_AUTHORITY, new CallLogContentProvider());
+ mCallLogContentProvider = new CallLogContentProvider();
+ contentResolver.addProvider(CALL_LOG_AUTHORITY, mCallLogContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -80,6 +83,12 @@ public final class CallLogQueryHelperTest {
}
@Test
+ public void testQueryWithSQLiteException() {
+ mCallLogContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50L));
+ }
+
+ @Test
public void testQueryIncomingCall() {
mCursor.addRow(new Object[] {
NORMALIZED_PHONE_NUMBER, /* date= */ 100L, /* duration= */ 30L,
@@ -159,11 +168,20 @@ public final class CallLogQueryHelperTest {
}
private class CallLogContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
+
return mCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 16a02b678511..1daee39ce9de 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -99,6 +99,14 @@ public final class ContactsQueryHelperTest {
}
@Test
+ public void testQueryOtherException_returnsFalse() {
+ contentProvider.setThrowOtherException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
public void testQueryIllegalArgumentException_returnsFalse() {
contentProvider.setThrowIllegalArgumentException(true);
@@ -152,6 +160,13 @@ public final class ContactsQueryHelperTest {
}
@Test
+ public void testQueryWithPhoneNumber_otherExceptionReturnsFalse() {
+ contentProvider.setThrowOtherException(true);
+ String contactUri = "tel:" + PHONE_NUMBER;
+ assertFalse(mHelper.query(contactUri));
+ }
+
+ @Test
public void testQueryWithEmail() {
mContactsLookupCursor.addRow(new Object[] {
/* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 0 });
@@ -188,6 +203,7 @@ public final class ContactsQueryHelperTest {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
private boolean mThrowSQLiteException = false;
private boolean mThrowIllegalArgumentException = false;
+ private boolean mThrowOtherException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
@@ -198,6 +214,9 @@ public final class ContactsQueryHelperTest {
if (mThrowIllegalArgumentException) {
throw new IllegalArgumentException();
}
+ if (mThrowOtherException) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
@@ -215,6 +234,10 @@ public final class ContactsQueryHelperTest {
this.mThrowIllegalArgumentException = throwException;
}
+ public void setThrowOtherException(boolean throwException) {
+ this.mThrowOtherException = throwException;
+ }
+
private void registerCursor(Uri uriPrefix, Cursor cursor) {
mUriPrefixToCursorMap.put(uriPrefix, cursor);
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
index 7730890e1486..9f4a43df6de8 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.Telephony.BaseMmsColumns;
import android.provider.Telephony.Mms;
@@ -63,6 +64,7 @@ public final class MmsQueryHelperTest {
private final List<MatrixCursor> mAddrCursors = new ArrayList<>();
private EventConsumer mEventConsumer;
private MmsQueryHelper mHelper;
+ private MmsContentProvider mMmsContentProvider;
@Before
public void setUp() {
@@ -73,7 +75,8 @@ public final class MmsQueryHelperTest {
mAddrCursors.add(new MatrixCursor(ADDR_COLUMNS));
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(MMS_AUTHORITY, new MmsContentProvider());
+ mMmsContentProvider = new MmsContentProvider();
+ contentResolver.addProvider(MMS_AUTHORITY, mMmsContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -87,6 +90,12 @@ public final class MmsQueryHelperTest {
}
@Test
+ public void testQueryWithSQLiteException() {
+ mMmsContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50_000L));
+ }
+
+ @Test
public void testQueryIncomingMessage() {
mMmsCursor.addRow(new Object[] {
/* id= */ 0, /* date= */ 100L, /* msgBox= */ BaseMmsColumns.MESSAGE_BOX_INBOX });
@@ -159,10 +168,15 @@ public final class MmsQueryHelperTest {
}
private class MmsContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
+
List<String> segments = uri.getPathSegments();
if (segments.size() == 2 && "addr".equals(segments.get(1))) {
int messageId = Integer.valueOf(segments.get(0));
@@ -170,5 +184,9 @@ public final class MmsQueryHelperTest {
}
return mMmsCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
index 5cb8cb4fe9f1..09a0dff77eb0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.Telephony.Sms;
import android.provider.Telephony.TextBasedSmsColumns;
@@ -59,6 +60,7 @@ public final class SmsQueryHelperTest {
private MatrixCursor mSmsCursor;
private EventConsumer mEventConsumer;
private SmsQueryHelper mHelper;
+ private SmsContentProvider mSmsContentProvider;
@Before
public void setUp() {
@@ -67,7 +69,8 @@ public final class SmsQueryHelperTest {
mSmsCursor = new MatrixCursor(SMS_COLUMNS);
MockContentResolver contentResolver = new MockContentResolver();
- contentResolver.addProvider(SMS_AUTHORITY, new SmsContentProvider());
+ mSmsContentProvider = new SmsContentProvider();
+ contentResolver.addProvider(SMS_AUTHORITY, mSmsContentProvider);
when(mContext.getContentResolver()).thenReturn(contentResolver);
mEventConsumer = new EventConsumer();
@@ -130,6 +133,12 @@ public final class SmsQueryHelperTest {
assertEquals(110L, events.get(1).getTimestamp());
}
+ @Test
+ public void testQueryWithSQLiteException() {
+ mSmsContentProvider.setThrowSQLiteException(true);
+ assertFalse(mHelper.querySince(50L));
+ }
+
private class EventConsumer implements BiConsumer<String, Event> {
private final Map<String, List<Event>> mEventMap = new ArrayMap<>();
@@ -141,11 +150,19 @@ public final class SmsQueryHelperTest {
}
private class SmsContentProvider extends MockContentProvider {
+ private boolean mThrowSQLiteException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (mThrowSQLiteException) {
+ throw new SQLiteException();
+ }
return mSmsCursor;
}
+
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt
new file mode 100644
index 000000000000..22d894a5a739
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.systemconfig
+
+import android.content.Context
+import android.content.pm.FeatureInfo
+import android.util.ArrayMap
+import android.util.Xml
+
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.SystemConfig
+import com.google.common.truth.Truth.assertThat
+import org.junit.runner.RunWith
+import org.junit.Rule
+
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SystemConfigReadOnlyFeaturesTest {
+
+ companion object {
+ private const val FEATURE_ONE = "feature.test.1"
+ private const val FEATURE_TWO = "feature.test.2"
+ private const val FEATURE_RUNTIME_AVAILABLE_TEMPLATE =
+ """
+ <permissions>
+ <feature name="%s" />
+ </permissions>
+ """
+ private const val FEATURE_RUNTIME_DISABLED_TEMPLATE =
+ """
+ <permissions>
+ <Disabled-feature name="%s" />
+ </permissions>
+ """
+
+ fun featureInfo(featureName: String) = FeatureInfo().apply { name = featureName }
+ }
+
+ private val context: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @get:Rule
+ val tempFolder = TemporaryFolder(context.filesDir)
+
+ private val injector = TestInjector()
+
+ private var uniqueCounter = 0
+
+ @Test
+ fun empty() {
+ assertFeatures().isEmpty()
+ }
+
+ @Test
+ fun readOnlyEnabled() {
+ addReadOnlyEnabledFeature(FEATURE_ONE)
+ addReadOnlyEnabledFeature(FEATURE_TWO)
+
+ assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO)
+ }
+
+ @Test
+ fun readOnlyAndRuntimeEnabled() {
+ addReadOnlyEnabledFeature(FEATURE_ONE)
+ addRuntimeEnabledFeature(FEATURE_TWO)
+
+ // No issues with matching availability.
+ assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO)
+ }
+
+ @Test
+ fun readOnlyEnabledRuntimeDisabled() {
+ addReadOnlyEnabledFeature(FEATURE_ONE)
+ addRuntimeDisabledFeature(FEATURE_ONE)
+
+ // Read-only feature availability should take precedence.
+ assertFeatures().contains(FEATURE_ONE)
+ }
+
+ @Test
+ fun readOnlyDisabled() {
+ addReadOnlyDisabledFeature(FEATURE_ONE)
+
+ assertFeatures().doesNotContain(FEATURE_ONE)
+ }
+
+ @Test
+ fun readOnlyAndRuntimeDisabled() {
+ addReadOnlyDisabledFeature(FEATURE_ONE)
+ addRuntimeDisabledFeature(FEATURE_ONE)
+
+ // No issues with matching (un)availability.
+ assertFeatures().doesNotContain(FEATURE_ONE)
+ }
+
+ @Test
+ fun readOnlyDisabledRuntimeEnabled() {
+ addReadOnlyDisabledFeature(FEATURE_ONE)
+ addRuntimeEnabledFeature(FEATURE_ONE)
+ addRuntimeEnabledFeature(FEATURE_TWO)
+
+ // Read-only feature (un)availability should take precedence.
+ assertFeatures().doesNotContain(FEATURE_ONE)
+ assertFeatures().contains(FEATURE_TWO)
+ }
+
+ fun addReadOnlyEnabledFeature(featureName: String) {
+ injector.readOnlyEnabledFeatures[featureName] = featureInfo(featureName)
+ }
+
+ fun addReadOnlyDisabledFeature(featureName: String) {
+ injector.readOnlyDisabledFeatures.add(featureName)
+ }
+
+ fun addRuntimeEnabledFeature(featureName: String) {
+ FEATURE_RUNTIME_AVAILABLE_TEMPLATE.format(featureName).write()
+ }
+
+ fun addRuntimeDisabledFeature(featureName: String) {
+ FEATURE_RUNTIME_DISABLED_TEMPLATE.format(featureName).write()
+ }
+
+ private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
+ .writeText(this.trimIndent())
+
+ private fun assertFeatures() = assertThat(availableFeatures().keys)
+
+ private fun availableFeatures() = SystemConfig(false, injector).apply {
+ val parser = Xml.newPullParser()
+ readPermissions(parser, tempFolder.root, /*Grant all permission flags*/ 0.inv())
+ }.let { it.availableFeatures }
+
+ internal class TestInjector() : SystemConfig.Injector() {
+ val readOnlyEnabledFeatures = ArrayMap<String, FeatureInfo>()
+ val readOnlyDisabledFeatures = mutableSetOf<String>()
+
+ override fun isReadOnlySystemEnabledFeature(featureName: String, version: Int): Boolean {
+ return readOnlyEnabledFeatures.containsKey(featureName)
+ }
+
+ override fun isReadOnlySystemDisabledFeature(featureName: String, version: Int): Boolean {
+ return readOnlyDisabledFeatures.contains(featureName)
+ }
+
+ override fun getReadOnlySystemEnabledFeatures(): ArrayMap<String, FeatureInfo> {
+ return readOnlyEnabledFeatures
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 457058849fca..79967b861ea5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -30,6 +30,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
@@ -260,6 +261,27 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
}
@Test
+ public void testUpdateInsetsControlPosition() {
+ final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+
+ final WindowState ime1 = createWindow(null, TYPE_INPUT_METHOD, "ime1");
+ ime1.getFrame().set(new Rect(0, 0, 0, 0));
+ mImeProvider.setWindowContainer(ime1, null, null);
+ mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
+ ime1.getFrame().set(new Rect(0, 400, 500, 500));
+ mImeProvider.updateInsetsControlPosition(ime1);
+ assertEquals(new Point(0, 400), mImeProvider.getControl(target).getSurfacePosition());
+
+ final WindowState ime2 = createWindow(null, TYPE_INPUT_METHOD, "ime2");
+ ime2.getFrame().set(new Rect(0, 0, 0, 0));
+ mImeProvider.setWindowContainer(ime2, null, null);
+ mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */);
+ ime2.getFrame().set(new Rect(0, 400, 500, 500));
+ mImeProvider.updateInsetsControlPosition(ime2);
+ assertEquals(new Point(0, 400), mImeProvider.getControl(target).getSurfacePosition());
+ }
+
+ @Test
public void testSetRequestedVisibleTypes() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp
index 9fb5aa21f985..dbdc4b4407b7 100644
--- a/tests/BootImageProfileTest/Android.bp
+++ b/tests/BootImageProfileTest/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_art_mainline",
}
java_test_host {
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 c77413b6a55a..c6855b4a97b4 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
@@ -59,6 +59,11 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
BOTTOM
}
+ enum class AppProperty {
+ STANDARD,
+ NON_RESIZABLE
+ }
+
/** Wait for an app moved to desktop to finish its transition. */
private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) {
wmHelper
diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index 28d7b42764c4..d78ced161eaf 100644
--- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -121,4 +121,12 @@ public class ProtoLogViewerConfigReaderTest {
assertNull(mConfig.getViewerString(4));
assertNull(mConfig.getViewerString(5));
}
+
+ @Test
+ public void loadUnloadAndReloadViewerConfig() {
+ loadViewerConfig();
+ unloadViewerConfig();
+ loadViewerConfig();
+ unloadViewerConfig();
+ }
}
diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java
index 10df17f991d3..6a8e142e2314 100644
--- a/tests/testables/src/android/testing/TestWithLooperRule.java
+++ b/tests/testables/src/android/testing/TestWithLooperRule.java
@@ -100,6 +100,9 @@ public class TestWithLooperRule implements MethodRule {
case "ExpectException":
next = this.getNextStatement(next, "next");
break;
+ case "UiThreadStatement":
+ next = this.getNextStatement(next, "base");
+ break;
default:
throw new Exception(
String.format("Unexpected Statement received: [%s]",
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index cba521e639cb..196b5e7c02ab 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -22,8 +22,6 @@ import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
-import java.util.HashMap
-import java.util.Map
import javax.lang.model.element.Modifier
/*
@@ -52,7 +50,7 @@ import javax.lang.model.element.Modifier
* public static boolean hasFeatureAutomotive(Context context);
* public static boolean hasFeatureLeanback(Context context);
* public static Boolean maybeHasFeature(String feature, int version);
- * public static ArrayMap<String, FeatureInfo> getCompileTimeAvailableFeatures();
+ * public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures();
* }
* </pre>
*/
@@ -63,6 +61,7 @@ object SystemFeaturesGenerator {
private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
+ private val ARRAYMAP_CLASS = ClassName.get("android.util", "ArrayMap")
private val ASSUME_TRUE_CLASS =
ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8")
private val ASSUME_FALSE_CLASS =
@@ -291,19 +290,19 @@ object SystemFeaturesGenerator {
features: Collection<FeatureInfo>,
) {
val methodBuilder =
- MethodSpec.methodBuilder("getCompileTimeAvailableFeatures")
+ MethodSpec.methodBuilder("getReadOnlySystemEnabledFeatures")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addAnnotation(ClassName.get("android.annotation", "NonNull"))
.addJavadoc("Gets features marked as available at compile-time, keyed by name." +
"\n\n@hide")
.returns(ParameterizedTypeName.get(
- ClassName.get(Map::class.java),
+ ARRAYMAP_CLASS,
ClassName.get(String::class.java),
FEATUREINFO_CLASS))
val availableFeatures = features.filter { it.readonly && it.version != null }
- methodBuilder.addStatement("Map<String, FeatureInfo> features = new \$T<>(\$L)",
- HashMap::class.java, availableFeatures.size)
+ methodBuilder.addStatement("\$T<String, FeatureInfo> features = new \$T<>(\$L)",
+ ARRAYMAP_CLASS, ARRAYMAP_CLASS, availableFeatures.size)
if (!availableFeatures.isEmpty()) {
methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()")
}
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
index edbfc4237547..ee97b26159de 100644
--- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -13,10 +13,9 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
+import android.util.ArrayMap;
import com.android.aconfig.annotations.AssumeFalseForR8;
import com.android.aconfig.annotations.AssumeTrueForR8;
-import java.util.HashMap;
-import java.util.Map;
/**
* @hide
@@ -94,8 +93,8 @@ public final class RoFeatures {
* @hide
*/
@NonNull
- public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
- Map<String, FeatureInfo> features = new HashMap<>(2);
+ public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>(2);
FeatureInfo fi = new FeatureInfo();
fi.name = PackageManager.FEATURE_WATCH;
fi.version = 1;
diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
index bf7a00679fa6..40c7db7ff1df 100644
--- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
@@ -9,8 +9,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
-import java.util.HashMap;
-import java.util.Map;
+import android.util.ArrayMap;
/**
* @hide
@@ -43,8 +42,8 @@ public final class RoNoFeatures {
* @hide
*/
@NonNull
- public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
- Map<String, FeatureInfo> features = new HashMap<>(0);
+ public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0);
return features;
}
}
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
index b20b228f9814..7bf89614b92d 100644
--- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -12,8 +12,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
-import java.util.HashMap;
-import java.util.Map;
+import android.util.ArrayMap;
/**
* @hide
@@ -73,8 +72,8 @@ public final class RwFeatures {
* @hide
*/
@NonNull
- public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
- Map<String, FeatureInfo> features = new HashMap<>(0);
+ public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0);
return features;
}
}
diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
index d91f5b62d8d4..eb7ec63f1d7d 100644
--- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
@@ -7,8 +7,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
-import java.util.HashMap;
-import java.util.Map;
+import android.util.ArrayMap;
/**
* @hide
@@ -32,8 +31,8 @@ public final class RwNoFeatures {
* @hide
*/
@NonNull
- public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
- Map<String, FeatureInfo> features = new HashMap<>(0);
+ public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0);
return features;
}
}
diff --git a/tools/systemfeatures/tests/src/ArrayMap.java b/tools/systemfeatures/tests/src/ArrayMap.java
new file mode 100644
index 000000000000..a5ed9b088896
--- /dev/null
+++ b/tools/systemfeatures/tests/src/ArrayMap.java
@@ -0,0 +1,26 @@
+/*
+ * 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.util;
+
+import java.util.HashMap;
+
+/** Stub for testing. */
+public final class ArrayMap<K, V> extends HashMap<K, V> {
+ public ArrayMap(int capacity) {
+ super(capacity);
+ }
+}
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
index 39f8fc44fe23..ed3f5c94ba79 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
@@ -60,7 +60,7 @@ public class SystemFeaturesGeneratorTest {
assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
- assertThat(RwNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
+ assertThat(RwNoFeatures.getReadOnlySystemEnabledFeatures()).isEmpty();
}
@Test
@@ -72,7 +72,7 @@ public class SystemFeaturesGeneratorTest {
assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
- assertThat(RoNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
+ assertThat(RoNoFeatures.getReadOnlySystemEnabledFeatures()).isEmpty();
// Also ensure we fall back to the PackageManager for feature APIs without an accompanying
// versioned feature definition.
@@ -106,7 +106,7 @@ public class SystemFeaturesGeneratorTest {
assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
- assertThat(RwFeatures.getCompileTimeAvailableFeatures()).isEmpty();
+ assertThat(RwFeatures.getReadOnlySystemEnabledFeatures()).isEmpty();
}
@Test
@@ -163,7 +163,7 @@ public class SystemFeaturesGeneratorTest {
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull();
assertThat(RoFeatures.maybeHasFeature("", 0)).isNull();
- Map<String, FeatureInfo> compiledFeatures = RoFeatures.getCompileTimeAvailableFeatures();
+ Map<String, FeatureInfo> compiledFeatures = RoFeatures.getReadOnlySystemEnabledFeatures();
assertThat(compiledFeatures.keySet())
.containsExactly(PackageManager.FEATURE_WATCH, PackageManager.FEATURE_WIFI);
assertThat(compiledFeatures.get(PackageManager.FEATURE_WATCH).version).isEqualTo(1);