summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig10
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig8
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java9
-rw-r--r--core/java/android/hardware/radio/Announcement.java2
-rw-r--r--core/java/android/hardware/radio/ProgramList.java21
-rw-r--r--core/java/android/hardware/radio/ProgramSelector.java91
-rw-r--r--core/java/android/hardware/radio/RadioManager.java291
-rw-r--r--core/java/android/hardware/radio/RadioMetadata.java48
-rw-r--r--core/java/android/view/WindowManager.java29
-rw-r--r--core/java/android/window/InputTransferToken.java3
-rw-r--r--core/java/android/window/flags/accessibility.aconfig3
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java30
-rw-r--r--core/java/com/android/internal/compat/compat_logging_flags.aconfig2
-rw-r--r--core/java/com/android/internal/policy/DecorView.java2
-rw-r--r--core/java/com/android/internal/widget/EmphasizedNotificationButton.java4
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java10
-rw-r--r--core/jni/android_window_InputTransferToken.cpp6
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/bugreports/OWNERS2
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java50
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java4
-rw-r--r--data/keyboards/Vendor_054c_Product_05c4.idc28
-rw-r--r--data/keyboards/Vendor_054c_Product_09cc.idc28
-rw-r--r--keystore/java/android/security/AndroidKeyStoreMaintenance.java14
-rw-r--r--keystore/java/android/security/KeyStore.java7
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java72
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java2
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java70
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt111
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java455
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt367
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java48
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml9
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml8
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java19
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt12
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java27
-rw-r--r--packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt215
-rw-r--r--packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt52
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt74
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt57
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt103
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt59
-rw-r--r--packages/SettingsProvider/Android.bp1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java5
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java37
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt10
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt135
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt30
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt17
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt10
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java6
-rw-r--r--packages/SystemUI/res/layout/window_magnification_settings_view.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml9
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackBounds.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackClipping.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackRounding.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt449
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt78
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt82
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt10
-rw-r--r--services/Android.bp1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java29
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java4
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java16
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java27
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java99
-rw-r--r--services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java10
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java51
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java10
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java30
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java7
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java53
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java6
-rw-r--r--services/core/java/com/android/server/display/config/LowBrightnessData.java142
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java11
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java4
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java14
-rw-r--r--services/core/java/com/android/server/pm/AppDataHelper.java60
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptJobService.java37
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java1152
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java38
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java408
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java68
-rw-r--r--services/core/java/com/android/server/pm/Installer.java291
-rw-r--r--services/core/java/com/android/server/pm/OWNERS1
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptService.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java241
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java121
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceInjector.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java463
-rw-r--r--services/core/java/com/android/server/pm/RemovePackageHelper.java49
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java266
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java40
-rw-r--r--services/core/java/com/android/server/pm/dex/DexManager.java184
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java14
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java2
-rw-r--r--services/core/java/com/android/server/wm/Task.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java35
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java5
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd20
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt13
-rw-r--r--services/devicepolicy/Android.bp1
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java110
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java204
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/RecursiveStringArrayResourceResolver.java147
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java51
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java684
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java184
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/RecursiveStringArrayResourceResolverTest.kt96
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java15
-rw-r--r--test-base/Android.bp39
-rw-r--r--test-base/hiddenapi/Android.bp7
-rw-r--r--test-junit/Android.bp53
-rw-r--r--test-junit/src/junit/MODULE_LICENSE_CPL (renamed from test-base/src/junit/MODULE_LICENSE_CPL)0
-rw-r--r--test-junit/src/junit/README.android (renamed from test-base/src/junit/README.android)0
-rw-r--r--test-junit/src/junit/cpl-v10.html (renamed from test-base/src/junit/cpl-v10.html)0
-rw-r--r--test-junit/src/junit/framework/Assert.java (renamed from test-base/src/junit/framework/Assert.java)0
-rw-r--r--test-junit/src/junit/framework/AssertionFailedError.java (renamed from test-base/src/junit/framework/AssertionFailedError.java)0
-rw-r--r--test-junit/src/junit/framework/ComparisonCompactor.java (renamed from test-base/src/junit/framework/ComparisonCompactor.java)0
-rw-r--r--test-junit/src/junit/framework/ComparisonFailure.java (renamed from test-base/src/junit/framework/ComparisonFailure.java)0
-rw-r--r--test-junit/src/junit/framework/Protectable.java (renamed from test-base/src/junit/framework/Protectable.java)0
-rw-r--r--test-junit/src/junit/framework/Test.java (renamed from test-base/src/junit/framework/Test.java)0
-rw-r--r--test-junit/src/junit/framework/TestCase.java (renamed from test-base/src/junit/framework/TestCase.java)0
-rw-r--r--test-junit/src/junit/framework/TestFailure.java (renamed from test-base/src/junit/framework/TestFailure.java)0
-rw-r--r--test-junit/src/junit/framework/TestListener.java (renamed from test-base/src/junit/framework/TestListener.java)0
-rw-r--r--test-junit/src/junit/framework/TestResult.java (renamed from test-base/src/junit/framework/TestResult.java)0
-rw-r--r--test-junit/src/junit/framework/TestSuite.java (renamed from test-base/src/junit/framework/TestSuite.java)0
-rw-r--r--test-junit/src/junit/runner/BaseTestRunner.java (renamed from test-runner/src/junit/runner/BaseTestRunner.java)0
-rw-r--r--test-junit/src/junit/runner/StandardTestSuiteLoader.java (renamed from test-runner/src/junit/runner/StandardTestSuiteLoader.java)0
-rw-r--r--test-junit/src/junit/runner/TestRunListener.java (renamed from test-runner/src/junit/runner/TestRunListener.java)0
-rw-r--r--test-junit/src/junit/runner/TestSuiteLoader.java (renamed from test-runner/src/junit/runner/TestSuiteLoader.java)0
-rw-r--r--test-junit/src/junit/runner/Version.java (renamed from test-runner/src/junit/runner/Version.java)0
-rw-r--r--test-junit/src/junit/runner/package-info.java (renamed from test-runner/src/junit/runner/package-info.java)0
-rw-r--r--test-junit/src/junit/textui/ResultPrinter.java (renamed from test-runner/src/junit/textui/ResultPrinter.java)0
-rw-r--r--test-junit/src/junit/textui/TestRunner.java (renamed from test-runner/src/junit/textui/TestRunner.java)0
-rw-r--r--test-junit/src/junit/textui/package-info.java (renamed from test-runner/src/junit/textui/package-info.java)0
-rw-r--r--test-mock/Android.bp7
-rw-r--r--test-runner/Android.bp24
-rw-r--r--test-runner/src/junit/MODULE_LICENSE_CPL0
-rw-r--r--test-runner/src/junit/README.android11
-rw-r--r--test-runner/src/junit/cpl-v10.html125
-rw-r--r--test-runner/tests/Android.bp7
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp5
-rw-r--r--tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java10
-rw-r--r--tools/hoststubgen/TEST_MAPPING3
257 files changed, 4903 insertions, 6481 deletions
diff --git a/Android.bp b/Android.bp
index 057b1d62ea5a..59e903ef37d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,7 +389,6 @@ java_defaults {
// TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
// system propagates "required" properly.
"gps_debug.conf",
- "protolog.conf.json.gz",
"core.protolog.pb",
"framework-res",
// any install dependencies should go into framework-minus-apex-install-dependencies
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 441d52148b7b..3ec6fe7728e5 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -1,3 +1,6 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+# proto-message: flag_declarations
+
package: "android.app.admin.flags"
flag {
@@ -180,3 +183,10 @@ flag {
description: "Allow COPE admin to control screen brightness and timeout."
bug: "323894620"
}
+
+flag {
+ name: "is_recursive_required_app_merging_enabled"
+ namespace: "enterprise"
+ description: "Guards a new flow for recursive required enterprise app list merging"
+ bug: "319084618"
+}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 24d6a5cfc42d..2904e7c989e8 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -44,3 +44,11 @@ flag {
description: "Enable device awareness in camera service"
bug: "305170199"
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "device_aware_drm"
+ description: "Makes MediaDrm APIs device-aware"
+ bug: "303535376"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 5b32f33777fa..c00e6101b363 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -1757,7 +1757,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mCallbacks, result.getSequenceId());
}
if ((!mSingleCapture) && (mPreviewProcessorType ==
- IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY)) {
+ IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY)
+ && mInitialized) {
CaptureStageImpl captureStage = null;
try {
captureStage = mPreviewRequestUpdateProcessor.process(
@@ -1780,8 +1781,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
} else {
mRequestUpdatedNeeded = false;
}
- } else if (mPreviewProcessorType ==
- IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
+ } else if ((mPreviewProcessorType ==
+ IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) && mInitialized) {
int idx = mPendingResultMap.indexOfKey(timestamp);
if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
@@ -1828,7 +1829,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
} else {
// No special handling for PROCESSOR_TYPE_NONE
}
- if (notifyClient) {
+ if (notifyClient && mInitialized) {
final long ident = Binder.clearCallingIdentity();
try {
if (processStatus) {
diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java
index 3ba3ebceeb18..faa103cf1da3 100644
--- a/core/java/android/hardware/radio/Announcement.java
+++ b/core/java/android/hardware/radio/Announcement.java
@@ -71,7 +71,7 @@ public final class Announcement implements Parcelable {
/**
* An event called whenever a list of active announcements change.
*
- * The entire list is sent each time a new announcement appears or any ends broadcasting.
+ * <p>The entire list is sent each time a new announcement appears or any ends broadcasting.
*
* @param activeAnnouncements a full list of active announcements
*/
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index c5167dbc7d4c..6146df8b4b1b 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -357,7 +357,7 @@ public final class ProgramList implements AutoCloseable {
/**
* Constructor of program list filter.
*
- * Arrays passed to this constructor become owned by this object, do not modify them later.
+ * <p>Arrays passed to this constructor will be owned by this object, do not modify them.
*
* @param identifierTypes see getIdentifierTypes()
* @param identifiers see getIdentifiers()
@@ -438,12 +438,11 @@ public final class ProgramList implements AutoCloseable {
/**
* Returns the list of identifier types that satisfy the filter.
*
- * If the program list entry contains at least one identifier of the type
- * listed, it satisfies this condition.
+ * <p>If the program list entry contains at least one identifier of the type
+ * listed, it satisfies this condition. Empty list means no filtering on
+ * identifier type.
*
- * Empty list means no filtering on identifier type.
- *
- * @return the list of accepted identifier types, must not be modified
+ * @return the set of accepted identifier types, must not be modified
*/
public @NonNull Set<Integer> getIdentifierTypes() {
return mIdentifierTypes;
@@ -452,12 +451,10 @@ public final class ProgramList implements AutoCloseable {
/**
* Returns the list of identifiers that satisfy the filter.
*
- * If the program list entry contains at least one listed identifier,
- * it satisfies this condition.
- *
- * Empty list means no filtering on identifier.
+ * <p>If the program list entry contains at least one listed identifier,
+ * it satisfies this condition. Empty list means no filtering on identifier.
*
- * @return the list of accepted identifiers, must not be modified
+ * @return the set of accepted identifiers, must not be modified
*/
public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() {
return mIdentifiers;
@@ -476,7 +473,7 @@ public final class ProgramList implements AutoCloseable {
/**
* Checks, if updates on entry modifications should be disabled.
*
- * If true, 'modified' vector of ProgramListChunk must contain list
+ * <p>If true, 'modified' vector of ProgramListChunk must contain list
* additions only. Once the program is added to the list, it's not
* updated anymore.
*/
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 0740374ad8e2..42028f67f400 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -36,27 +36,31 @@ import java.util.stream.Stream;
/**
* A set of identifiers necessary to tune to a given station.
*
- * This can hold various identifiers, like
- * - AM/FM frequency
- * - HD Radio subchannel
- * - DAB channel info
+ * <p>This can hold various identifiers, like
+ * <ui>
+ * <li>AM/FM frequency</li>
+ * <li>HD Radio subchannel</li>
+ * <li>DAB channel info</li>
+ * </ui>
*
- * The primary ID uniquely identifies a station and can be used for equality
+ * <p>The primary ID uniquely identifies a station and can be used for equality
* check. The secondary IDs are supplementary and can speed up tuning process,
* but the primary ID is sufficient (ie. after a full band scan).
*
- * Two selectors with different secondary IDs, but the same primary ID are
+ * <p>Two selectors with different secondary IDs, but the same primary ID are
* considered equal. In particular, secondary IDs vector may get updated for
* an entry on the program list (ie. when a better frequency for a given
* station is found).
*
- * The primaryId of a given programType MUST be of a specific type:
- * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
- * - AM_HD, FM_HD: HD_STATION_ID_EXT;
- * - DAB: DAB_SIDECC;
- * - DRMO: DRMO_SERVICE_ID;
- * - SXM: SXM_SERVICE_ID;
- * - VENDOR: VENDOR_PRIMARY.
+ * <p>The primaryId of a given programType MUST be of a specific type:
+ * <ui>
+ * <li>AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;</li>
+ * <li>AM_HD, FM_HD: HD_STATION_ID_EXT;</li>
+ * <li>DAB: DAB_SIDECC;</li>
+ * <li>DRMO: DRMO_SERVICE_ID;</li>
+ * <li>SXM: SXM_SERVICE_ID;</li>
+ * <li>VENDOR: VENDOR_PRIMARY.</li>
+ * </ui>
* @hide
*/
@SystemApi
@@ -258,10 +262,10 @@ public final class ProgramSelector implements Parcelable {
/**
* 64bit additional identifier for HD Radio.
*
- * <p>Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
- * globally unique. To provide a best-effort solution, a short version of
- * station name may be carried as additional identifier and may be used
- * by the tuner hardware to double-check tuning.
+ * <p>Due to Station ID abuse, some {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT}
+ * identifiers may be not globally unique. To provide a best-effort solution, a
+ * short version of station name may be carried as additional identifier and
+ * may be used by the tuner hardware to double-check tuning.
*
* <p>The name is limited to the first 8 A-Z0-9 characters (lowercase
* letters must be converted to uppercase). Encoded in little-endian
@@ -384,7 +388,7 @@ public final class ProgramSelector implements Parcelable {
* The value format is determined by a vendor.
*
* <p>It must not be used in any other programType than corresponding VENDOR
- * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
+ * type between VENDOR_START and VENDOR_END (e.g. identifier type 1015 must
* not be used in any program type other than 1015).
*/
public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
@@ -435,9 +439,10 @@ public final class ProgramSelector implements Parcelable {
/**
* Constructor for ProgramSelector.
*
- * It's not desired to modify selector objects, so all its fields are initialized at creation.
+ * <p>It's not desired to modify selector objects, so all its fields are initialized at
+ * creation.
*
- * Identifier lists must not contain any nulls, but can itself be null to be interpreted
+ * <p>Identifier lists must not contain any nulls, but can itself be null to be interpreted
* as empty list at object creation.
*
* @param programType type of a radio technology.
@@ -492,8 +497,8 @@ public final class ProgramSelector implements Parcelable {
/**
* Looks up an identifier of a given type (either primary or secondary).
*
- * If there are multiple identifiers if a given type, then first in order (where primary id is
- * before any secondary) is selected.
+ * <p>If there are multiple identifiers if a given type, then first in order (where primary id
+ * is before any secondary) is selected.
*
* @param type type of identifier.
* @return identifier value, if found.
@@ -510,11 +515,11 @@ public final class ProgramSelector implements Parcelable {
/**
* Looks up all identifier of a given type (either primary or secondary).
*
- * Some identifiers may be provided multiple times, for example
- * IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
+ * <p>Some identifiers may be provided multiple times, for example
+ * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} for FM Alternate Frequencies.
*
* @param type type of identifier.
- * @return a list of identifiers, generated on each call. May be modified.
+ * @return an array of identifiers, generated on each call. May be modified.
*/
public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
List<Identifier> out = new ArrayList<>();
@@ -543,14 +548,14 @@ public final class ProgramSelector implements Parcelable {
/**
* Creates an equivalent ProgramSelector with a given secondary identifier preferred.
*
- * Used to point to a specific physical identifier for technologies that may broadcast the same
- * program on different channels. For example, with a DAB program broadcasted over multiple
+ * <p>Used to point to a specific physical identifier for technologies that may broadcast the
+ * same program on different channels. For example, with a DAB program broadcasted over multiple
* ensembles, the radio hardware may select the one with the strongest signal. The UI may select
* preferred ensemble though, so the radio hardware may try to use it in the first place.
*
- * This is a best-effort hint for the tuner, not a guaranteed behavior.
+ * <p>This is a best-effort hint for the tuner, not a guaranteed behavior.
*
- * Setting the given secondary identifier as preferred means filtering out other secondary
+ * <p>Setting the given secondary identifier as preferred means filtering out other secondary
* identifiers of its type and adding it to the list.
*
* @param preferred preferred secondary identifier
@@ -577,7 +582,7 @@ public final class ProgramSelector implements Parcelable {
*
* @param band the band.
* @param frequencyKhz the frequency in kHz.
- * @return new ProgramSelector object representing given frequency.
+ * @return new {@link ProgramSelector} object representing given frequency.
* @throws IllegalArgumentException if provided frequency is out of bounds.
*/
public static @NonNull ProgramSelector createAmFmSelector(
@@ -588,13 +593,13 @@ public final class ProgramSelector implements Parcelable {
/**
* Checks, if a given AM/FM frequency is roughly valid and in correct unit.
*
- * It does not check the range precisely: it may provide false positives, but not false
+ * <p>It does not check the range precisely: it may provide false positives, but not false
* negatives. In particular, it may be way off for certain regions.
- * The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
+ * The main purpose is to avoid passing improper units, ie. MHz instead of kHz.
*
* @param isAm true, if AM, false if FM.
* @param frequencyKhz the frequency in kHz.
- * @return true, if the frequency is rougly valid.
+ * @return true, if the frequency is roughly valid.
*/
private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
if (isAm) {
@@ -607,7 +612,7 @@ public final class ProgramSelector implements Parcelable {
/**
* Builds new ProgramSelector for AM/FM frequency.
*
- * This method variant supports HD Radio subchannels, but it's undesirable to
+ * <p>This method variant supports HD Radio subchannels, but it's undesirable to
* select them manually. Instead, the value should be retrieved from program list.
*
* @param band the band.
@@ -741,9 +746,9 @@ public final class ProgramSelector implements Parcelable {
};
/**
- * A single program identifier component, eg. frequency or channel ID.
+ * A single program identifier component, e.g. frequency or channel ID.
*
- * The long value field holds the value in format described in comments for
+ * <p>The long value field holds the value in format described in comments for
* IdentifierType constants.
*/
public static final class Identifier implements Parcelable {
@@ -776,11 +781,11 @@ public final class ProgramSelector implements Parcelable {
}
/**
- * Returns whether this Identifier's type is considered a category when filtering
+ * Returns whether this identifier's type is considered a category when filtering
* ProgramLists for category entries.
*
* @see ProgramList.Filter#areCategoriesIncluded
- * @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
+ * @return False if this identifier's type is not tunable (e.g. DAB ensemble or
* vendor-specified type). True otherwise.
*/
public boolean isCategoryType() {
@@ -791,14 +796,14 @@ public final class ProgramSelector implements Parcelable {
/**
* Value of an identifier.
*
- * Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
- * the value is a frequency in kHz.
+ * <p>Its meaning depends on identifier type, ie. for
+ * {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} type, the value is a frequency in kHz.
*
- * The range of a value depends on its type; it does not always require the whole long
+ * <p>The range of a value depends on its type; it does not always require the whole long
* range. Casting to necessary type (ie. int) without range checking is correct in front-end
* code - any range violations are either errors in the framework or in the
- * HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
- * as Integer.MAX_VALUE would mean 2.1THz.
+ * HAL implementation. For example, {@link #IDENTIFIER_TYPE_AMFM_FREQUENCY} always fits in
+ * int, as {@link Integer#MAX_VALUE} would mean 2.1THz.
*
* @return value of an identifier.
*/
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index da6c68646820..61854e44287b 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -102,7 +102,7 @@ public class RadioManager {
public @interface RadioStatusType{}
- // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
+ // keep in sync with radio_class_t in /system/core/include/system/radio.h
/** Radio module class supporting FM (including HD radio) and AM */
public static final int CLASS_AM_FM = 0;
/** Radio module class supporting satellite radio */
@@ -154,7 +154,7 @@ public class RadioManager {
/**
* Forces mono audio stream reception.
*
- * Analog broadcasts can recover poor reception conditions by jointing
+ * <p>Analog broadcasts can recover poor reception conditions by jointing
* stereo channels into one. Mainly for, but not limited to AM/FM.
*/
public static final int CONFIG_FORCE_MONO = 1;
@@ -176,7 +176,7 @@ public class RadioManager {
/**
* Forces the digital playback for the supporting radio technology.
*
- * User may disable digital-analog handover that happens with poor
+ * <p>User may disable digital-analog handover that happens with poor
* reception conditions. With digital forced, the radio will remain silent
* instead of switching to analog channel if it's available. This is purely
* user choice, it does not reflect the actual state of handover.
@@ -185,7 +185,7 @@ public class RadioManager {
/**
* RDS Alternative Frequencies.
*
- * If set and the currently tuned RDS station broadcasts on multiple
+ * <p>If set and the currently tuned RDS station broadcasts on multiple
* channels, radio tuner automatically switches to the best available
* alternative.
*/
@@ -193,7 +193,7 @@ public class RadioManager {
/**
* RDS region-specific program lock-down.
*
- * Allows user to lock to the current region as they move into the
+ * <p>Allows user to lock to the current region as they move into the
* other region.
*/
public static final int CONFIG_RDS_REG = 5;
@@ -247,11 +247,12 @@ public class RadioManager {
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigFlag {}
- /*****************************************************************************
+ /**
* Lists properties, options and radio bands supported by a given broadcast radio module.
- * Each module has a unique ID used to address it when calling RadioManager APIs.
- * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method.
- ****************************************************************************/
+ *
+ * <p>Each module has a unique ID used to address it when calling RadioManager APIs.
+ * Module properties are returned by {@link #listModules(List)} method.
+ */
public static class ModuleProperties implements Parcelable {
private final int mId;
@@ -315,8 +316,11 @@ public class RadioManager {
return set.stream().mapToInt(Integer::intValue).toArray();
}
- /** Unique module identifier provided by the native service.
- * For use with {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
+ /**
+ * Unique module identifier provided by the native service.
+ *
+ * <p>or use with
+ * {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
* @return the radio module unique identifier.
*/
public int getId() {
@@ -324,22 +328,24 @@ public class RadioManager {
}
/**
- * Module service (driver) name as registered with HIDL.
+ * Module service (driver) name as registered with HIDL or AIDL HAL.
* @return the module service name.
*/
public @NonNull String getServiceName() {
return mServiceName;
}
- /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT}
+ /**
+ * Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT}
* @return the radio module class identifier.
*/
public int getClassId() {
return mClassId;
}
- /** Human readable broadcast radio module implementor
- * @return the name of the radio module implementator.
+ /**
+ * Human readable broadcast radio module implementor
+ * @return the name of the radio module implementer.
*/
public String getImplementor() {
return mImplementor;
@@ -352,31 +358,38 @@ public class RadioManager {
return mProduct;
}
- /** Human readable broadcast radio module version number
+ /**
+ * Human readable broadcast radio module version number
* @return the radio module version.
*/
public String getVersion() {
return mVersion;
}
- /** Radio module serial number.
- * Can be used for subscription services.
+ /**
+ * Radio module serial number.
+ *
+ * <p>This can be used for subscription services.
* @return the radio module serial number.
*/
public String getSerial() {
return mSerial;
}
- /** Number of tuners available.
- * This is the number of tuners that can be open simultaneously.
+ /**
+ * Number of tuners available.
+ *
+ * <p>This is the number of tuners that can be open simultaneously.
* @return the number of tuners supported.
*/
public int getNumTuners() {
return mNumTuners;
}
- /** Number tuner audio sources available. Must be less or equal to getNumTuners().
- * When more than one tuner is supported, one is usually for playback and has one
+ /**
+ * Number tuner audio sources available. Must be less or equal to {@link #getNumTuners}.
+ *
+ * <p>When more than one tuner is supported, one is usually for playback and has one
* associated audio source and the other is for pre scanning and building a
* program list.
* @return the number of audio sources available.
@@ -387,20 +400,24 @@ public class RadioManager {
}
/**
- * Checks, if BandConfig initialization (after {@link RadioManager#openTuner})
+ * Checks, if {@link BandConfig} initialization (after {@link RadioManager#openTuner})
* is required to be done before other operations or not.
*
- * If it is, the client has to wait for {@link RadioTuner.Callback#onConfigurationChanged}
- * callback before executing any other operations. Otherwise, such operation will fail
- * returning {@link RadioManager#STATUS_INVALID_OPERATION} error code.
+ * <p>If it is, the client has to wait for
+ * {@link RadioTuner.Callback#onConfigurationChanged} callback before executing any other
+ * operations. Otherwise, such operation will fail returning
+ * {@link RadioManager#STATUS_INVALID_OPERATION} error code.
*/
public boolean isInitializationRequired() {
return mIsInitializationRequired;
}
- /** {@code true} if audio capture is possible from radio tuner output.
- * This indicates if routing to audio devices not connected to the same HAL as the FM radio
- * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented.
+ /**
+ * {@code true} if audio capture is possible from radio tuner output.
+ *
+ * <p>This indicates if routing to audio devices not connected to the same HAL as the FM
+ * radio is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be
+ * implemented.
* @return {@code true} if audio capture is possible, {@code false} otherwise.
*/
public boolean isCaptureSupported() {
@@ -421,8 +438,8 @@ public class RadioManager {
/**
* Checks, if a given program type is supported by this tuner.
*
- * If a program type is supported by radio module, it means it can tune
- * to ProgramSelector of a given type.
+ * <p>If a program type is supported by radio module, it means it can tune
+ * to {@link ProgramSelector} of a given type.
*
* @return {@code true} if a given program type is supported.
*/
@@ -433,8 +450,8 @@ public class RadioManager {
/**
* Checks, if a given program identifier is supported by this tuner.
*
- * If an identifier is supported by radio module, it means it can use it for
- * tuning to ProgramSelector with either primary or secondary Identifier of
+ * <p>If an identifier is supported by radio module, it means it can use it for
+ * tuning to {@link ProgramSelector} with either primary or secondary Identifier of
* a given type.
*
* @return {@code true} if a given program type is supported.
@@ -446,9 +463,9 @@ public class RadioManager {
/**
* A frequency table for Digital Audio Broadcasting (DAB).
*
- * The key is a channel name, i.e. 5A, 7B.
+ * <p>The key is a channel name, i.e. 5A, 7B.
*
- * The value is a frequency, in kHz.
+ * <p>The value is a frequency, in kHz.
*
* @return a frequency table, or {@code null} if the module doesn't support DAB
*/
@@ -460,17 +477,18 @@ public class RadioManager {
* A map of vendor-specific opaque strings, passed from HAL without changes.
* Format of these strings can vary across vendors.
*
- * It may be used for extra features, that's not supported by a platform,
+ * <p>It may be used for extra features, that's not supported by a platform,
* for example: preset-slots=6; ultra-hd-capable=false.
*
- * Keys must be prefixed with unique vendor Java-style namespace,
- * eg. 'com.somecompany.parameter1'.
+ * <p>Keys must be prefixed with unique vendor Java-style namespace,
+ * e.g. 'com.somecompany.parameter1'.
*/
public @NonNull Map<String, String> getVendorInfo() {
return mVendorInfo;
}
- /** List of descriptors for all bands supported by this module.
+ /**
+ * List of descriptors for all bands supported by this module.
* @return an array of {@link BandDescriptor}.
*/
public BandDescriptor[] getBands() {
@@ -590,7 +608,9 @@ public class RadioManager {
}
/** Radio band descriptor: an element in ModuleProperties bands array.
- * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */
+ *
+ * <p>It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor}
+ */
public static class BandDescriptor implements Parcelable {
private final int mRegion;
@@ -610,16 +630,18 @@ public class RadioManager {
mSpacing = spacing;
}
- /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+ /**
+ * Region this band applies to. E.g. {@link #REGION_ITU_1}
* @return the region this band is associated to.
*/
public int getRegion() {
return mRegion;
}
- /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ /**
+ * Band type, e.g. {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
* <ul>
- * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
- * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}</li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}</li>
* </ul>
* @return the band type.
*/
@@ -645,23 +667,29 @@ public class RadioManager {
return mType == BAND_FM || mType == BAND_FM_HD;
}
- /** Lower band limit expressed in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
+ /**
+ * Lower band limit expressed in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz.
* @return the lower band limit.
*/
public int getLowerLimit() {
return mLowerLimit;
}
- /** Upper band limit expressed in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
+ /**
+ * Upper band limit expressed in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz.
* @return the upper band limit.
*/
public int getUpperLimit() {
return mUpperLimit;
}
- /** Channel spacing in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
- * @return the channel spacing.
+ /**
+ * Channel spacing in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz
+ * @return the channel spacing.</p>
*/
public int getSpacing() {
return mSpacing;
@@ -758,9 +786,11 @@ public class RadioManager {
}
}
- /** FM band descriptor
+ /**
+ * FM band descriptor
* @see #BAND_FM
- * @see #BAND_FM_HD */
+ * @see #BAND_FM_HD
+ */
public static class FmBandDescriptor extends BandDescriptor {
private final boolean mStereo;
private final boolean mRds;
@@ -779,19 +809,22 @@ public class RadioManager {
mEa = ea;
}
- /** Stereo is supported
+ /**
+ * Stereo is supported
* @return {@code true} if stereo is supported, {@code false} otherwise.
*/
public boolean isStereoSupported() {
return mStereo;
}
- /** RDS or RBDS(if region is ITU2) is supported
+ /**
+ * RDS or RBDS(if region is ITU2) is supported
* @return {@code true} if RDS or RBDS is supported, {@code false} otherwise.
*/
public boolean isRdsSupported() {
return mRds;
}
- /** Traffic announcement is supported
+ /**
+ * Traffic announcement is supported
* @return {@code true} if TA is supported, {@code false} otherwise.
*/
public boolean isTaSupported() {
@@ -804,8 +837,9 @@ public class RadioManager {
return mAf;
}
- /** Emergency Announcement is supported
- * @return {@code true} if Emergency annoucement is supported, {@code false} otherwise.
+ /**
+ * Emergency Announcement is supported
+ * @return {@code true} if Emergency announcement is supported, {@code false} otherwise.
*/
public boolean isEaSupported() {
return mEa;
@@ -890,8 +924,10 @@ public class RadioManager {
}
}
- /** AM band descriptor.
- * @see #BAND_AM */
+ /**
+ * AM band descriptor.
+ * @see #BAND_AM
+ */
public static class AmBandDescriptor extends BandDescriptor {
private final boolean mStereo;
@@ -903,8 +939,9 @@ public class RadioManager {
mStereo = stereo;
}
- /** Stereo is supported
- * @return {@code true} if stereo is supported, {@code false} otherwise.
+ /**
+ * Stereo is supported
+ * @return {@code true} if stereo is supported, {@code false} otherwise.
*/
public boolean isStereoSupported() {
return mStereo;
@@ -991,39 +1028,47 @@ public class RadioManager {
return mDescriptor;
}
- /** Region this band applies to. E.g. {@link #REGION_ITU_1}
- * @return the region associated with this band.
+ /**
+ * Region this band applies to. E.g. {@link #REGION_ITU_1}
+ * @return the region associated with this band.
*/
public int getRegion() {
return mDescriptor.getRegion();
}
- /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ /**
+ * Band type, e.g. {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
* <ul>
- * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
- * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}</li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}</li>
* </ul>
* @return the band type.
*/
public int getType() {
return mDescriptor.getType();
}
- /** Lower band limit expressed in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
- * @return the lower band limit.
+ /**
+ * Lower band limit expressed in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz.
+ * @return the lower band limit.
*/
public int getLowerLimit() {
return mDescriptor.getLowerLimit();
}
- /** Upper band limit expressed in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
- * @return the upper band limit.
+ /**
+ * Upper band limit expressed in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz.
+ * @return the upper band limit.
*/
public int getUpperLimit() {
return mDescriptor.getUpperLimit();
}
- /** Channel spacing in units according to band type.
- * Currently all defined band types express channels as frequency in kHz
- * @return the channel spacing.
+ /**
+ * Channel spacing in units according to band type.
+ *
+ * <p>Currently all defined band types express channels as frequency in kHz.
+ * @return the channel spacing.
*/
public int getSpacing() {
return mDescriptor.getSpacing();
@@ -1089,9 +1134,11 @@ public class RadioManager {
}
}
- /** FM band configuration.
+ /**
+ * FM band configuration.
* @see #BAND_FM
- * @see #BAND_FM_HD */
+ * @see #BAND_FM_HD
+ */
public static class FmBandConfig extends BandConfig {
private final boolean mStereo;
private final boolean mRds;
@@ -1119,28 +1166,32 @@ public class RadioManager {
mEa = ea;
}
- /** Get stereo enable state
+ /**
+ * Get stereo enable state
* @return the enable state.
*/
public boolean getStereo() {
return mStereo;
}
- /** Get RDS or RBDS(if region is ITU2) enable state
+ /**
+ * Get RDS or RBDS(if region is ITU2) enable state
* @return the enable state.
*/
public boolean getRds() {
return mRds;
}
- /** Get Traffic announcement enable state
+ /**
+ * Get Traffic announcement enable state
* @return the enable state.
*/
public boolean getTa() {
return mTa;
}
- /** Get Alternate Frequency Switching enable state
+ /**
+ * Get Alternate Frequency Switching enable state
* @return the enable state.
*/
public boolean getAf() {
@@ -1285,7 +1336,8 @@ public class RadioManager {
return config;
}
- /** Set stereo enable state
+ /**
+ * Set stereo enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1294,7 +1346,8 @@ public class RadioManager {
return this;
}
- /** Set RDS or RBDS(if region is ITU2) enable state
+ /**
+ * Set RDS or RBDS(if region is ITU2) enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1303,7 +1356,8 @@ public class RadioManager {
return this;
}
- /** Set Traffic announcement enable state
+ /**
+ * Set Traffic announcement enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1312,7 +1366,8 @@ public class RadioManager {
return this;
}
- /** Set Alternate Frequency Switching enable state
+ /**
+ * Set Alternate Frequency Switching enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1321,7 +1376,8 @@ public class RadioManager {
return this;
}
- /** Set Emergency Announcement enable state
+ /**
+ * Set Emergency Announcement enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1332,8 +1388,10 @@ public class RadioManager {
};
}
- /** AM band configuration.
- * @see #BAND_AM */
+ /**
+ * AM band configuration.
+ * @see #BAND_AM
+ */
public static class AmBandConfig extends BandConfig {
private final boolean mStereo;
@@ -1349,7 +1407,8 @@ public class RadioManager {
mStereo = stereo;
}
- /** Get stereo enable state
+ /**
+ * Get stereo enable state
* @return the enable state.
*/
public boolean getStereo() {
@@ -1453,7 +1512,8 @@ public class RadioManager {
return config;
}
- /** Set stereo enable state
+ /**
+ * Set stereo enable state
* @param state The new enable state.
* @return the same Builder instance.
*/
@@ -1467,7 +1527,8 @@ public class RadioManager {
/** Radio program information. */
public static class ProgramInfo implements Parcelable {
- // sourced from hardware/interfaces/broadcastradio/2.0/types.hal
+ // sourced from
+ // hardware/interfaces/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
private static final int FLAG_LIVE = 1 << 0;
private static final int FLAG_MUTED = 1 << 1;
private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2;
@@ -1521,10 +1582,10 @@ public class RadioManager {
/**
* Identifier currently used for program selection.
*
- * This identifier can be used to determine which technology is
+ * <p>This identifier can be used to determine which technology is
* currently being used for reception.
*
- * Some program selectors contain tuning information for different radio
+ * <p>Some program selectors contain tuning information for different radio
* technologies (i.e. FM RDS and DAB). For example, user may tune using
* a ProgramSelector with RDS_PI primary identifier, but the tuner hardware
* may choose to use DAB technology to make actual tuning. This identifier
@@ -1537,7 +1598,7 @@ public class RadioManager {
/**
* Identifier currently used by hardware to physically tune to a channel.
*
- * Some radio technologies broadcast the same program on multiple channels,
+ * <p>Some radio technologies broadcast the same program on multiple channels,
* i.e. with RDS AF the same program may be broadcasted on multiple
* alternative frequencies; the same DAB program may be broadcast on
* multiple ensembles. This identifier points to the channel to which the
@@ -1550,11 +1611,11 @@ public class RadioManager {
/**
* Primary identifiers of related contents.
*
- * Some radio technologies provide pointers to other programs that carry
+ * <p>Some radio technologies provide pointers to other programs that carry
* related content (i.e. DAB soft-links). This field is a list of pointers
* to other programs on the program list.
*
- * Please note, that these identifiers does not have to exist on the program
+ * <p>Please note, that these identifiers does not have to exist on the program
* list - i.e. DAB tuner may provide information on FM RDS alternatives
* despite not supporting FM RDS. If the system has multiple tuners, another
* one may have it on its list.
@@ -1563,7 +1624,8 @@ public class RadioManager {
return mRelatedContent;
}
- /** Main channel expressed in units according to band type.
+ /**
+ * Main channel expressed in units according to band type.
* Currently all defined band types express channels as frequency in kHz
* @return the program channel
* @deprecated Use {@link ProgramInfo#getSelector} instead.
@@ -1578,7 +1640,8 @@ public class RadioManager {
}
}
- /** Sub channel ID. E.g 1 for HD radio HD1
+ /**
+ * Sub channel ID. E.g. 1 for HD radio HD1
* @return the program sub channel
* @deprecated Use {@link ProgramInfo#getSelector} instead.
*/
@@ -1600,14 +1663,16 @@ public class RadioManager {
return (mInfoFlags & FLAG_TUNED) != 0;
}
- /** {@code true} if the received program is stereo
+ /**
+ * {@code true} if the received program is stereo
* @return {@code true} if stereo, {@code false} otherwise.
*/
public boolean isStereo() {
return (mInfoFlags & FLAG_STEREO) != 0;
}
- /** {@code true} if the received program is digital (e.g HD radio)
+ /**
+ * {@code true} if the received program is digital (e.g. HD radio)
* @return {@code true} if digital, {@code false} otherwise.
* @deprecated Use {@link ProgramInfo#getLogicallyTunedTo()} instead.
*/
@@ -1623,8 +1688,9 @@ public class RadioManager {
/**
* {@code true} if the program is currently playing live stream.
- * This may result in a slightly altered reception parameters,
- * usually targetted at reduced latency.
+ *
+ * <p>This may result in a slightly altered reception parameters,
+ * usually targeted at reduced latency.
*/
public boolean isLive() {
return (mInfoFlags & FLAG_LIVE) != 0;
@@ -1634,7 +1700,8 @@ public class RadioManager {
* {@code true} if radio stream is not playing, i.e. due to bad reception
* conditions or buffering. In this state volume knob MAY be disabled to
* prevent user increasing volume too much.
- * It does NOT mean the user has muted audio.
+ *
+ * <p>It does NOT mean the user has muted audio.
*/
public boolean isMuted() {
return (mInfoFlags & FLAG_MUTED) != 0;
@@ -1688,8 +1755,9 @@ public class RadioManager {
}
/** Metadata currently received from this station.
- * null if no metadata have been received
- * @return current meta data received from this program.
+ *
+ * @return current meta data received from this program, {@code null} if no metadata have
+ * been received
*/
public RadioMetadata getMetadata() {
return mMetadata;
@@ -1699,11 +1767,11 @@ public class RadioManager {
* A map of vendor-specific opaque strings, passed from HAL without changes.
* Format of these strings can vary across vendors.
*
- * It may be used for extra features, that's not supported by a platform,
+ * <p>It may be used for extra features, that's not supported by a platform,
* for example: paid-service=true; bitrate=320kbps.
*
- * Keys must be prefixed with unique vendor Java-style namespace,
- * eg. 'com.somecompany.parameter1'.
+ * <p>Keys must be prefixed with unique vendor Java-style namespace,
+ * e.g. 'com.somecompany.parameter1'.
*/
public @NonNull Map<String, String> getVendorInfo() {
return mVendorInfo;
@@ -1830,13 +1898,14 @@ public class RadioManager {
/**
* Open an interface to control a tuner on a given broadcast radio module.
- * Optionally selects and applies the configuration passed as "config" argument.
+ *
+ * <p>Optionally selects and applies the configuration passed as "config" argument.
* @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory.
* @param config desired band and configuration to apply when enabling the hardware module.
* optional, can be null.
* @param withAudio {@code true} to request a tuner with an audio source.
* This tuner is intended for live listening or recording or a radio program.
- * If {@code false}, the tuner can only be used to retrieve program informations.
+ * If {@code false}, the tuner can only be used to retrieve program information.
* @param callback {@link RadioTuner.Callback} interface. Mandatory.
* @param handler the Handler on which the callbacks will be received.
* Can be null if default handler is OK.
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index 67381ec8e829..31880fd405a8 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -291,7 +291,7 @@ public final class RadioMetadata implements Parcelable {
/**
* Provides a Clock that can be used to describe time as provided by the Radio.
*
- * The clock is defined by the seconds since epoch at the UTC + 0 timezone
+ * <p>The clock time is defined by the seconds since epoch at the UTC + 0 timezone
* and timezone offset from UTC + 0 represented in number of minutes.
*
* @hide
@@ -493,16 +493,16 @@ public final class RadioMetadata implements Parcelable {
/**
* Retrieves an identifier for a bitmap.
*
- * The format of an identifier is opaque to the application,
+ * <p>The format of an identifier is opaque to the application,
* with a special case of value 0 being invalid.
* An identifier for a given image-tuner pair is unique, so an application
* may cache images and determine if there is a necessity to fetch them
* again - if identifier changes, it means the image has changed.
*
- * Only bitmap keys may be used with this method:
+ * <p>Only bitmap keys may be used with this method:
* <ul>
- * <li>{@link #METADATA_KEY_ICON}</li>
- * <li>{@link #METADATA_KEY_ART}</li>
+ * <li>{@link #METADATA_KEY_ICON}</li>
+ * <li>{@link #METADATA_KEY_ART}</li>
* </ul>
*
* @param key The key the value is stored under.
@@ -537,7 +537,7 @@ public final class RadioMetadata implements Parcelable {
*
* <p>Only string array keys may be used with this method:
* <ul>
- * <li>{@link #METADATA_KEY_UFIDS}</li>
+ * <li>{@link #METADATA_KEY_UFIDS}</li>
* </ul>
*
* @param key The key the value is stored under
@@ -667,17 +667,17 @@ public final class RadioMetadata implements Parcelable {
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
* <ul>
- * <li>{@link #METADATA_KEY_RDS_PS}</li>
- * <li>{@link #METADATA_KEY_RDS_RT}</li>
- * <li>{@link #METADATA_KEY_TITLE}</li>
- * <li>{@link #METADATA_KEY_ARTIST}</li>
- * <li>{@link #METADATA_KEY_ALBUM}</li>
- * <li>{@link #METADATA_KEY_GENRE}</li>
- * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li>
- * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li>
- * <li>{@link #METADATA_KEY_COMMERCIAL}</li>
- * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li>
- * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li>
+ * <li>{@link #METADATA_KEY_RDS_PS}</li>
+ * <li>{@link #METADATA_KEY_RDS_RT}</li>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li>
+ * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li>
+ * <li>{@link #METADATA_KEY_COMMERCIAL}</li>
+ * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li>
+ * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li>
* </ul>
*
* @param key The key for referencing this value
@@ -699,10 +699,10 @@ public final class RadioMetadata implements Parcelable {
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
* <ul>
- * <li>{@link #METADATA_KEY_RDS_PI}</li>
- * <li>{@link #METADATA_KEY_RDS_PTY}</li>
- * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
- * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li>
+ * <li>{@link #METADATA_KEY_RDS_PI}</li>
+ * <li>{@link #METADATA_KEY_RDS_PTY}</li>
+ * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+ * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li>
* </ul>
* or any bitmap represented by its identifier.
*
@@ -720,8 +720,8 @@ public final class RadioMetadata implements Parcelable {
* if the METADATA_KEYs defined in this class are used they may only be
* one of the following:
* <ul>
- * <li>{@link #METADATA_KEY_ICON}</li>
- * <li>{@link #METADATA_KEY_ART}</li>
+ * <li>{@link #METADATA_KEY_ICON}</li>
+ * <li>{@link #METADATA_KEY_ART}</li>
* </ul>
* <p>
*
@@ -765,7 +765,7 @@ public final class RadioMetadata implements Parcelable {
* the METADATA_KEYs defined in this class are used they may only be one
* of the following:
* <ul>
- * <li>{@link #METADATA_KEY_UFIDS}</li>
+ * <li>{@link #METADATA_KEY_UFIDS}</li>
* </ul>
*
* @param key The key for referencing this value
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 86fc6f48a145..56667398265e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1476,15 +1476,26 @@ public interface WindowManager extends ViewManager {
*/
@TestApi
static boolean hasWindowExtensionsEnabled() {
- return HAS_WINDOW_EXTENSIONS_ON_DEVICE
- && ActivityTaskManager.supportsMultiWindow(ActivityThread.currentApplication())
- // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
- // on all devices by default as a build file property.
- // Until finishing flag ramp up, only return true when
- // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
- // OEMs.
- && (Flags.enableWmExtensionsForAllFlag()
- || !ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15);
+ if (!Flags.enableWmExtensionsForAllFlag() && ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15) {
+ // Since enableWmExtensionsForAllFlag, HAS_WINDOW_EXTENSIONS_ON_DEVICE is now true
+ // on all devices by default as a build file property.
+ // Until finishing flag ramp up, only return true when
+ // ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15 is false, which is set per device by
+ // OEMs.
+ return false;
+ }
+
+ if (!HAS_WINDOW_EXTENSIONS_ON_DEVICE) {
+ return false;
+ }
+
+ try {
+ return ActivityTaskManager.supportsMultiWindow(ActivityThread.currentApplication());
+ } catch (Exception e) {
+ // In case the PackageManager is not set up correctly in test.
+ Log.e("WindowManager", "Unable to read if the device supports multi window", e);
+ return false;
+ }
}
/**
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index 5fab48f93316..d2cefa8e0570 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -57,6 +57,7 @@ public final class InputTransferToken implements Parcelable {
private static native void nativeWriteToParcel(long nativeObject, Parcel out);
private static native long nativeReadFromParcel(Parcel in);
private static native IBinder nativeGetBinderToken(long nativeObject);
+ private static native long nativeGetBinderTokenRef(long nativeObject);
private static native long nativeGetNativeInputTransferTokenFinalizer();
private static native boolean nativeEquals(long nativeObject1, long nativeObject2);
@@ -130,7 +131,7 @@ public final class InputTransferToken implements Parcelable {
*/
@Override
public int hashCode() {
- return Objects.hash(getToken());
+ return Objects.hash(nativeGetBinderTokenRef(mNativeObject));
}
/**
diff --git a/core/java/android/window/flags/accessibility.aconfig b/core/java/android/window/flags/accessibility.aconfig
index 814c62017391..90b54bd76a60 100644
--- a/core/java/android/window/flags/accessibility.aconfig
+++ b/core/java/android/window/flags/accessibility.aconfig
@@ -12,4 +12,7 @@ flag {
namespace: "accessibility"
description: "Always draw fullscreen orange border in fullscreen magnification"
bug: "291891390"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 78f06b6bddb3..84715aa80edb 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -217,6 +217,12 @@ public class ResolverActivity extends Activity implements
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
/**
+ * Boolean extra to indicate if Resolver Sheet needs to be started in single user mode.
+ */
+ protected static final String EXTRA_RESTRICT_TO_SINGLE_USER =
+ "com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER";
+
+ /**
* Integer extra to indicate which profile should be automatically selected.
* <p>Can only be used if there is a work profile.
* <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
@@ -750,8 +756,10 @@ public class ResolverActivity extends Activity implements
}
protected UserHandle getPersonalProfileUserHandle() {
- if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
- return mPrivateProfileUserHandle;
+ // When launched in single user mode, only personal tab is populated, so we use
+ // tabOwnerUserHandleForLaunch as personal tab's user handle.
+ if (privateSpaceEnabled() && isLaunchedInSingleUserMode()) {
+ return getTabOwnerUserHandleForLaunch();
}
return mPersonalProfileUserHandle;
}
@@ -822,11 +830,11 @@ public class ResolverActivity extends Activity implements
// If we are in work or private profile's process, return WorkProfile/PrivateProfile user
// as owner, otherwise we always return PersonalProfile user as owner
if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
- return getWorkProfileUserHandle();
+ return mWorkProfileUserHandle;
} else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
- return getPrivateProfileUserHandle();
+ return mPrivateProfileUserHandle;
}
- return getPersonalProfileUserHandle();
+ return mPersonalProfileUserHandle;
}
private boolean hasWorkProfile() {
@@ -847,8 +855,18 @@ public class ResolverActivity extends Activity implements
&& (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
}
+ protected final boolean isLaunchedInSingleUserMode() {
+ // When launched from Private Profile, return true
+ if (isLaunchedAsPrivateProfile()) {
+ return true;
+ }
+ return getIntent()
+ .getBooleanExtra(EXTRA_RESTRICT_TO_SINGLE_USER, /* defaultValue = */ false);
+ }
+
protected boolean shouldShowTabs() {
- if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ // No Tabs are shown when launched in single user mode.
+ if (privateSpaceEnabled() && isLaunchedInSingleUserMode()) {
return false;
}
return hasWorkProfile() && ENABLE_TABBED_VIEW;
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
index fab3856daca7..a5c31edde473 100644
--- a/core/java/com/android/internal/compat/compat_logging_flags.aconfig
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -2,7 +2,7 @@ package: "com.android.internal.compat.flags"
flag {
name: "skip_old_and_disabled_compat_logging"
- namespace: "platform_compat"
+ namespace: "app_compat"
description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
bug: "323949942"
is_fixed_read_only: true
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 0f1f7e9900c1..a65a1bb18303 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -137,7 +137,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private static final int SCRIM_LIGHT = 0xe6ffffff; // 90% white
- private static final int SCRIM_ALPHA = 0xcc0000; // 80% alpha
+ private static final int SCRIM_ALPHA = 0xcc000000; // 80% alpha
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 3e065bf9f450..01b45697f5d4 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -171,7 +171,9 @@ public class EmphasizedNotificationButton extends Button {
return;
}
- prepareIcon(icon);
+ if (icon != null) {
+ prepareIcon(icon);
+ }
mIconToGlue = icon;
mGluePending = true;
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 5da64350619c..352e6d8e7b59 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -31,6 +31,8 @@ import android.view.RemotableViewMethod;
import android.widget.RemoteViews;
import android.widget.TextView;
+import com.android.internal.R;
+
/**
* A TextView that can float around an image on the end.
*
@@ -49,6 +51,7 @@ public class ImageFloatingTextView extends TextView {
private int mMaxLinesForHeight = -1;
private int mLayoutMaxLines = -1;
private int mImageEndMargin;
+ private final int mMaxLineUpperLimit;
private int mStaticLayoutCreationCountInOnMeasure = 0;
@@ -71,6 +74,8 @@ public class ImageFloatingTextView extends TextView {
super(context, attrs, defStyleAttr, defStyleRes);
setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
+ mMaxLineUpperLimit =
+ getResources().getInteger(R.integer.config_notificationLongTextMaxLineCount);
}
@Override
@@ -102,6 +107,11 @@ public class ImageFloatingTextView extends TextView {
} else {
maxLines = getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE;
}
+
+ if (mMaxLineUpperLimit > 0) {
+ maxLines = Math.min(maxLines, mMaxLineUpperLimit);
+ }
+
builder.setMaxLines(maxLines);
mLayoutMaxLines = maxLines;
if (shouldEllipsize) {
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
index 8fb668d6bbd9..5bcea9b7c401 100644
--- a/core/jni/android_window_InputTransferToken.cpp
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -70,6 +70,11 @@ static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj)
return javaObjectForIBinder(env, inputTransferToken->mToken);
}
+static jlong nativeGetBinderTokenRef(JNIEnv*, jclass, jlong nativeObj) {
+ sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ return reinterpret_cast<jlong>(inputTransferToken->mToken.get());
+}
+
InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
JNIEnv* env, jobject inputTransferTokenObj) {
if (inputTransferTokenObj != nullptr &&
@@ -114,6 +119,7 @@ static const JNINativeMethod sInputTransferTokenMethods[] = {
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
{"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
{"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+ {"nativeGetBinderTokenRef", "(J)J", (void*)nativeGetBinderTokenRef},
{"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
{"nativeEquals", "(JJ)Z", (void*) nativeEquals},
// clang-format on
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e3f1cb619eb5..efba7099d678 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1483,6 +1483,11 @@
<!-- Number of notifications to keep in the notification service historical archive -->
<integer name="config_notificationServiceArchiveSize">100</integer>
+ <!-- Upper limit imposed for long text content for BigTextStyle, MessagingStyle and
+ ConversationStyle notifications for performance reasons, and that line count is also
+ capped by vertical space available. It is only enabled when the value is positive int.-->
+ <integer name="config_notificationLongTextMaxLineCount">10</integer>
+
<!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f4b42f6b3fb2..668a88c4370a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2078,6 +2078,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+ <java-symbol type="integer" name="config_notificationLongTextMaxLineCount" />
<java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
<java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" />
<java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" />
diff --git a/core/tests/bugreports/OWNERS b/core/tests/bugreports/OWNERS
new file mode 100644
index 000000000000..dbd767c78853
--- /dev/null
+++ b/core/tests/bugreports/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 153446
+file:/platform/frameworks/native:/cmds/dumpstate/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index cb8754ae9962..488f017872b1 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -27,6 +27,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.android.internal.app.MatcherUtils.first;
+import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER;
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
@@ -1254,6 +1255,51 @@ public class ResolverActivityTest {
}
}
+ @Test
+ public void testTriggerFromMainProfile_inSingleUserMode_withWorkProfilePresent() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markWorkProfileUserAvailable();
+ setTabOwnerUserHandleForLaunch(PERSONAL_USER_HANDLE);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getPersonalListAdapter().getCount(), is(2));
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle, PERSONAL_USER_HANDLE);
+ }
+ }
+
+ @Test
+ public void testTriggerFromWorkProfile_inSingleUserMode() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markWorkProfileUserAvailable();
+ setTabOwnerUserHandleForLaunch(sOverrides.workProfileUserHandle);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.workProfileUserHandle);
+ }
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1339,6 +1385,10 @@ public class ResolverActivityTest {
ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
}
+ private void setTabOwnerUserHandleForLaunch(UserHandle tabOwnerUserHandleForLaunch) {
+ sOverrides.tabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 862cbd5b5e01..4604b01d1bd2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -116,6 +116,10 @@ public class ResolverWrapperActivity extends ResolverActivity {
when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
return sOverrides.resolverListController;
}
+ if (isLaunchedInSingleUserMode()) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.resolverListController;
+ }
when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
return sOverrides.workResolverListController;
}
diff --git a/data/keyboards/Vendor_054c_Product_05c4.idc b/data/keyboards/Vendor_054c_Product_05c4.idc
index 2cb3f7b90fed..9576e8d042ba 100644
--- a/data/keyboards/Vendor_054c_Product_05c4.idc
+++ b/data/keyboards/Vendor_054c_Product_05c4.idc
@@ -13,9 +13,11 @@
# limitations under the License.
#
-# Sony DS4 motion sensor configuration file.
+# Sony Playstation(R) DualShock 4 Controller
#
+## Motion sensor ##
+
# reporting mode 0 - continuous
sensor.accelerometer.reportingMode = 0
# The delay between sensor events corresponding to the lowest frequency in microsecond
@@ -33,3 +35,27 @@ sensor.gyroscope.maxDelay = 100000
sensor.gyroscope.minDelay = 5000
# The power in mA used by this sensor while in use
sensor.gyroscope.power = 0.8
+
+## Touchpad ##
+
+# After the DualShock 4 has been connected over Bluetooth for a minute or so,
+# its reports start bunching up in time, meaning that we receive 2–4 reports
+# within a millisecond followed by a >10ms wait until the next batch.
+#
+# This uneven timing causes the apparent speed of a finger (calculated using
+# time deltas between received reports) to vary dramatically even if it's
+# actually moving smoothly across the touchpad, triggering the touchpad stack's
+# drumroll detection logic, which causes the finger's single smooth movement to
+# be treated as many small movements of consecutive touches, which are then
+# inhibited by the click wiggle filter.
+#
+# Since this touchpad does not seem vulnerable to click wiggle, we can safely
+# disable drumroll detection due to speed changes (by setting the speed change
+# threshold very high, since there's no boolean control property).
+gestureProp.Drumroll_Max_Speed_Change_Factor = 1000000000
+
+# Because of the way this touchpad is positioned, touches around the edges are
+# no more likely to be palms than ones in the middle, so remove the edge zones
+# from the palm classifier to increase the usable area of the pad.
+gestureProp.Palm_Edge_Zone_Width = 0
+gestureProp.Tap_Exclusion_Border_Width = 0
diff --git a/data/keyboards/Vendor_054c_Product_09cc.idc b/data/keyboards/Vendor_054c_Product_09cc.idc
index 2cb3f7b90fed..9576e8d042ba 100644
--- a/data/keyboards/Vendor_054c_Product_09cc.idc
+++ b/data/keyboards/Vendor_054c_Product_09cc.idc
@@ -13,9 +13,11 @@
# limitations under the License.
#
-# Sony DS4 motion sensor configuration file.
+# Sony Playstation(R) DualShock 4 Controller
#
+## Motion sensor ##
+
# reporting mode 0 - continuous
sensor.accelerometer.reportingMode = 0
# The delay between sensor events corresponding to the lowest frequency in microsecond
@@ -33,3 +35,27 @@ sensor.gyroscope.maxDelay = 100000
sensor.gyroscope.minDelay = 5000
# The power in mA used by this sensor while in use
sensor.gyroscope.power = 0.8
+
+## Touchpad ##
+
+# After the DualShock 4 has been connected over Bluetooth for a minute or so,
+# its reports start bunching up in time, meaning that we receive 2–4 reports
+# within a millisecond followed by a >10ms wait until the next batch.
+#
+# This uneven timing causes the apparent speed of a finger (calculated using
+# time deltas between received reports) to vary dramatically even if it's
+# actually moving smoothly across the touchpad, triggering the touchpad stack's
+# drumroll detection logic, which causes the finger's single smooth movement to
+# be treated as many small movements of consecutive touches, which are then
+# inhibited by the click wiggle filter.
+#
+# Since this touchpad does not seem vulnerable to click wiggle, we can safely
+# disable drumroll detection due to speed changes (by setting the speed change
+# threshold very high, since there's no boolean control property).
+gestureProp.Drumroll_Max_Speed_Change_Factor = 1000000000
+
+# Because of the way this touchpad is positioned, touches around the edges are
+# no more likely to be palms than ones in the middle, so remove the edge zones
+# from the palm classifier to increase the usable area of the pad.
+gestureProp.Palm_Edge_Zone_Width = 0
+gestureProp.Tap_Exclusion_Border_Width = 0
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 2430e8d8e662..efbbfc23736f 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -175,20 +175,6 @@ public class AndroidKeyStoreMaintenance {
}
/**
- * Informs Keystore 2.0 that an off body event was detected.
- */
- public static void onDeviceOffBody() {
- StrictMode.noteDiskWrite();
- try {
- getService().onDeviceOffBody();
- } catch (Exception e) {
- // TODO This fails open. This is not a regression with respect to keystore1 but it
- // should get fixed.
- Log.e(TAG, "Error while reporting device off body event.", e);
- }
- }
-
- /**
* Migrates a key given by the source descriptor to the location designated by the destination
* descriptor.
*
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index bd9abec22325..f105072a32bf 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -56,11 +56,4 @@ public class KeyStore {
return Authorization.addAuthToken(authToken);
}
-
- /**
- * Notify keystore that the device went off-body.
- */
- public void onDeviceOffBody() {
- AndroidKeyStoreMaintenance.onDeviceOffBody();
- }
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 7aecfd8d4a0d..d359a9050a0f 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -880,9 +880,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
}
/**
- * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
- * signing. Encryption and signature verification will still be available when the screen is
- * locked.
+ * Returns {@code true} if the key is authorized to be used only while the device is unlocked.
*
* @see Builder#setUnlockedDeviceRequired(boolean)
*/
@@ -1672,16 +1670,16 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
* {@link #setUserAuthenticationValidityDurationSeconds} and
* {@link #setUserAuthenticationRequired}). Once the device has been removed from the
* user's body, the key will be considered unauthorized and the user will need to
- * re-authenticate to use it. For keys without an authentication validity period this
- * parameter has no effect.
- *
- * <p>Similarly, on devices that do not have an on-body sensor, this parameter will have no
- * effect; the device will always be considered to be "on-body" and the key will therefore
- * remain authorized until the validity period ends.
- *
- * @param remainsValid if {@code true}, and if the device supports on-body detection, key
- * will be invalidated when the device is removed from the user's body or when the
- * authentication validity expires, whichever occurs first.
+ * re-authenticate to use it. If the device does not have an on-body sensor or the key does
+ * not have an authentication validity period, this parameter has no effect.
+ * <p>
+ * Since Android 12 (API level 31), this parameter has no effect even on devices that have
+ * an on-body sensor. A future version of Android may restore enforcement of this parameter.
+ * Meanwhile, it is recommended to not use it.
+ *
+ * @param remainsValid if {@code true}, and if the device supports enforcement of this
+ * parameter, the key will be invalidated when the device is removed from the user's body or
+ * when the authentication validity expires, whichever occurs first.
*/
@NonNull
public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
@@ -1723,11 +1721,49 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
}
/**
- * Sets whether the keystore requires the screen to be unlocked before allowing decryption
- * using this key. If this is set to {@code true}, any attempt to decrypt or sign using this
- * key while the screen is locked will fail. A locked device requires a PIN, password,
- * biometric, or other trusted factor to access. While the screen is locked, any associated
- * public key can still be used (e.g for signature verification).
+ * Sets whether this key is authorized to be used only while the device is unlocked.
+ * <p>
+ * The device is considered to be locked for a user when the user's apps are currently
+ * inaccessible and some form of lock screen authentication is required to regain access to
+ * them. For the full definition, see {@link KeyguardManager#isDeviceLocked()}.
+ * <p>
+ * Public key operations aren't restricted by {@code setUnlockedDeviceRequired(true)} and
+ * may be performed even while the device is locked. In Android 11 (API level 30) and lower,
+ * encryption and verification operations with symmetric keys weren't restricted either.
+ * <p>
+ * Keys that use {@code setUnlockedDeviceRequired(true)} can be imported and generated even
+ * while the device is locked, as long as the device has been unlocked at least once since
+ * the last reboot. However, such keys cannot be used (except for the unrestricted
+ * operations mentioned above) until the device is unlocked. Apps that need to encrypt data
+ * while the device is locked such that it can only be decrypted while the device is
+ * unlocked can generate a key and encrypt the data in software, import the key into
+ * Keystore using {@code setUnlockedDeviceRequired(true)}, and zeroize the original key.
+ * <p>
+ * {@code setUnlockedDeviceRequired(true)} is related to but distinct from
+ * {@link #setUserAuthenticationRequired(boolean) setUserAuthenticationRequired(true)}.
+ * {@code setUnlockedDeviceRequired(true)} requires that the device be unlocked, whereas
+ * {@code setUserAuthenticationRequired(true)} requires that a specific type of strong
+ * authentication has happened within a specific time period. They may be used together or
+ * separately; there are cases in which one requirement can be satisfied but not the other.
+ * <p>
+ * <b>Warning:</b> Be careful using {@code setUnlockedDeviceRequired(true)} on Android 14
+ * (API level 34) and lower, since the following bugs existed in Android 12 through 14:
+ * <ul>
+ * <li>When the user didn't have a secure lock screen, unlocked-device-required keys
+ * couldn't be generated, imported, or used.</li>
+ * <li>When the user's secure lock screen was removed, all of that user's
+ * unlocked-device-required keys were automatically deleted.</li>
+ * <li>Unlocking the device with a non-strong biometric, such as face on many devices,
+ * didn't re-authorize the use of unlocked-device-required keys.</li>
+ * <li>Unlocking the device with a biometric didn't re-authorize the use of
+ * unlocked-device-required keys in profiles that share their parent user's lock.</li>
+ * </ul>
+ * These issues are fixed in Android 15, so apps can avoid them by using
+ * {@code setUnlockedDeviceRequired(true)} only on Android 15 and higher.
+ * Apps that use both {@code setUnlockedDeviceRequired(true)} and
+ * {@link #setUserAuthenticationRequired(boolean) setUserAuthenticationRequired(true)}
+ * are unaffected by the first two issues, since the first two issues describe expected
+ * behavior for {@code setUserAuthenticationRequired(true)}.
*/
@NonNull
public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) {
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 5cffe46936a2..2163ca2f8217 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -279,7 +279,7 @@ public class KeyInfo implements KeySpec {
}
/**
- * Returns {@code true} if the key is authorized to be used only when the device is unlocked.
+ * Returns {@code true} if the key is authorized to be used only while the device is unlocked.
*
* <p>This authorization applies only to secret key and private key operations. Public key
* operations are not restricted.
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 31b4a5eac619..8e5ac45d394d 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -577,9 +577,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
}
/**
- * Returns {@code true} if the screen must be unlocked for this key to be used for decryption or
- * signing. Encryption and signature verification will still be available when the screen is
- * locked.
+ * Returns {@code true} if the key is authorized to be used only while the device is unlocked.
*
* @see Builder#setUnlockedDeviceRequired(boolean)
*/
@@ -1039,16 +1037,16 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
* {@link #setUserAuthenticationValidityDurationSeconds} and
* {@link #setUserAuthenticationRequired}). Once the device has been removed from the
* user's body, the key will be considered unauthorized and the user will need to
- * re-authenticate to use it. For keys without an authentication validity period this
- * parameter has no effect.
+ * re-authenticate to use it. If the device does not have an on-body sensor or the key does
+ * not have an authentication validity period, this parameter has no effect.
+ * <p>
+ * Since Android 12 (API level 31), this parameter has no effect even on devices that have
+ * an on-body sensor. A future version of Android may restore enforcement of this parameter.
+ * Meanwhile, it is recommended to not use it.
*
- * <p>Similarly, on devices that do not have an on-body sensor, this parameter will have no
- * effect; the device will always be considered to be "on-body" and the key will therefore
- * remain authorized until the validity period ends.
- *
- * @param remainsValid if {@code true}, and if the device supports on-body detection, key
- * will be invalidated when the device is removed from the user's body or when the
- * authentication validity expires, whichever occurs first.
+ * @param remainsValid if {@code true}, and if the device supports enforcement of this
+ * parameter, the key will be invalidated when the device is removed from the user's body or
+ * when the authentication validity expires, whichever occurs first.
*/
@NonNull
public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
@@ -1117,11 +1115,49 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
}
/**
- * Sets whether the keystore requires the screen to be unlocked before allowing decryption
- * using this key. If this is set to {@code true}, any attempt to decrypt or sign using this
- * key while the screen is locked will fail. A locked device requires a PIN, password,
- * biometric, or other trusted factor to access. While the screen is locked, the key can
- * still be used for encryption or signature verification.
+ * Sets whether this key is authorized to be used only while the device is unlocked.
+ * <p>
+ * The device is considered to be locked for a user when the user's apps are currently
+ * inaccessible and some form of lock screen authentication is required to regain access to
+ * them. For the full definition, see {@link KeyguardManager#isDeviceLocked()}.
+ * <p>
+ * Public key operations aren't restricted by {@code setUnlockedDeviceRequired(true)} and
+ * may be performed even while the device is locked. In Android 11 (API level 30) and lower,
+ * encryption and verification operations with symmetric keys weren't restricted either.
+ * <p>
+ * Keys that use {@code setUnlockedDeviceRequired(true)} can be imported and generated even
+ * while the device is locked, as long as the device has been unlocked at least once since
+ * the last reboot. However, such keys cannot be used (except for the unrestricted
+ * operations mentioned above) until the device is unlocked. Apps that need to encrypt data
+ * while the device is locked such that it can only be decrypted while the device is
+ * unlocked can generate a key and encrypt the data in software, import the key into
+ * Keystore using {@code setUnlockedDeviceRequired(true)}, and zeroize the original key.
+ * <p>
+ * {@code setUnlockedDeviceRequired(true)} is related to but distinct from
+ * {@link #setUserAuthenticationRequired(boolean) setUserAuthenticationRequired(true)}.
+ * {@code setUnlockedDeviceRequired(true)} requires that the device be unlocked, whereas
+ * {@code setUserAuthenticationRequired(true)} requires that a specific type of strong
+ * authentication has happened within a specific time period. They may be used together or
+ * separately; there are cases in which one requirement can be satisfied but not the other.
+ * <p>
+ * <b>Warning:</b> Be careful using {@code setUnlockedDeviceRequired(true)} on Android 14
+ * (API level 34) and lower, since the following bugs existed in Android 12 through 14:
+ * <ul>
+ * <li>When the user didn't have a secure lock screen, unlocked-device-required keys
+ * couldn't be generated, imported, or used.</li>
+ * <li>When the user's secure lock screen was removed, all of that user's
+ * unlocked-device-required keys were automatically deleted.</li>
+ * <li>Unlocking the device with a non-strong biometric, such as face on many devices,
+ * didn't re-authorize the use of unlocked-device-required keys.</li>
+ * <li>Unlocking the device with a biometric didn't re-authorize the use of
+ * unlocked-device-required keys in profiles that share their parent user's lock.</li>
+ * </ul>
+ * These issues are fixed in Android 15, so apps can avoid them by using
+ * {@code setUnlockedDeviceRequired(true)} only on Android 15 and higher.
+ * Apps that use both {@code setUnlockedDeviceRequired(true)} and
+ * {@link #setUserAuthenticationRequired(boolean) setUserAuthenticationRequired(true)}
+ * are unaffected by the first two issues, since the first two issues describe expected
+ * behavior for {@code setUserAuthenticationRequired(true)}.
*/
@NonNull
public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) {
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index e422198c40c5..e73d8802f0b2 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -26,6 +26,7 @@ import android.view.WindowManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
import com.android.wm.shell.common.bubbles.BubbleBarLocation
@@ -54,6 +55,7 @@ class BubblePositionerTest {
@Before
fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
val windowManager = context.getSystemService(WindowManager::class.java)
positioner = BubblePositioner(context, windowManager)
}
@@ -167,8 +169,9 @@ class BubblePositionerTest {
@Test
fun testGetRestingPosition_afterBoundsChange() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true,
- windowBounds = Rect(0, 0, 2000, 1600)))
+ positioner.update(
+ defaultDeviceConfig.copy(isLargeScreen = true, windowBounds = Rect(0, 0, 2000, 1600))
+ )
// Set the resting position to the right side
var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
@@ -176,8 +179,9 @@ class BubblePositionerTest {
positioner.restingPosition = restingPosition
// Now make the device smaller
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = false,
- windowBounds = Rect(0, 0, 1000, 1600)))
+ positioner.update(
+ defaultDeviceConfig.copy(isLargeScreen = false, windowBounds = Rect(0, 0, 1000, 1600))
+ )
// Check the resting position is on the correct side
allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
@@ -236,7 +240,8 @@ class BubblePositionerTest {
0 /* taskId */,
null /* locus */,
true /* isDismissable */,
- directExecutor()) {}
+ directExecutor()
+ ) {}
// Ensure the height is the same as the desired value
assertThat(positioner.getExpandedViewHeight(bubble))
@@ -263,7 +268,8 @@ class BubblePositionerTest {
0 /* taskId */,
null /* locus */,
true /* isDismissable */,
- directExecutor()) {}
+ directExecutor()
+ ) {}
// Ensure the height is the same as the desired value
val minHeight =
@@ -471,20 +477,20 @@ class BubblePositionerTest {
fun testGetTaskViewContentWidth_onLeft() {
positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
- val paddings = positioner.getExpandedViewContainerPadding(true /* onLeft */,
- false /* isOverflow */)
- assertThat(taskViewWidth).isEqualTo(
- positioner.screenRect.width() - paddings[0] - paddings[2])
+ val paddings =
+ positioner.getExpandedViewContainerPadding(true /* onLeft */, false /* isOverflow */)
+ assertThat(taskViewWidth)
+ .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
}
@Test
fun testGetTaskViewContentWidth_onRight() {
positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
- val paddings = positioner.getExpandedViewContainerPadding(false /* onLeft */,
- false /* isOverflow */)
- assertThat(taskViewWidth).isEqualTo(
- positioner.screenRect.width() - paddings[0] - paddings[2])
+ val paddings =
+ positioner.getExpandedViewContainerPadding(false /* onLeft */, false /* isOverflow */)
+ assertThat(taskViewWidth)
+ .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
}
@Test
@@ -513,6 +519,66 @@ class BubblePositionerTest {
assertThat(positioner.isBubbleBarOnLeft).isFalse()
}
+ @Test
+ fun testGetBubbleBarExpandedViewBounds_onLeft() {
+ testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false)
+ }
+
+ @Test
+ fun testGetBubbleBarExpandedViewBounds_onRight() {
+ testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false)
+ }
+
+ @Test
+ fun testGetBubbleBarExpandedViewBounds_isOverflow_onLeft() {
+ testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true)
+ }
+
+ @Test
+ fun testGetBubbleBarExpandedViewBounds_isOverflow_onRight() {
+ testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
+ }
+
+ private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
+ positioner.setShowingInBubbleBar(true)
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ isLargeScreen = true,
+ isLandscape = true,
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 2000, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ positioner.bubbleBarBounds = getBubbleBarBounds(onLeft, deviceConfig)
+
+ val expandedViewPadding =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+
+ val left: Int
+ val right: Int
+ if (onLeft) {
+ // Pin to the left, calculate right
+ left = deviceConfig.insets.left + expandedViewPadding
+ right = left + positioner.getExpandedViewWidthForBubbleBar(isOverflow)
+ } else {
+ // Pin to the right, calculate left
+ right =
+ deviceConfig.windowBounds.right - deviceConfig.insets.right - expandedViewPadding
+ left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow)
+ }
+ // Above the bubble bar
+ val bottom = positioner.bubbleBarBounds.top - expandedViewPadding
+ // Calculate right and top based on size
+ val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow)
+ val expectedBounds = Rect(left, top, right, bottom)
+
+ val bounds = Rect()
+ positioner.getBubbleBarExpandedViewBounds(onLeft, isOverflow, bounds)
+
+ assertThat(bounds).isEqualTo(expectedBounds)
+ }
+
private val defaultYPosition: Float
/**
* Calculates the Y position bubbles should be placed based on the config. Based on the
@@ -544,4 +610,21 @@ class BubblePositionerTest {
positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
}
+
+ private fun getBubbleBarBounds(onLeft: Boolean, deviceConfig: DeviceConfig): Rect {
+ val width = 200
+ val height = 100
+ val bottom = deviceConfig.windowBounds.bottom - deviceConfig.insets.bottom
+ val top = bottom - height
+ val left: Int
+ val right: Int
+ if (onLeft) {
+ left = deviceConfig.insets.left
+ right = left + width
+ } else {
+ right = deviceConfig.windowBounds.right - deviceConfig.insets.right
+ left = right - width
+ }
+ return Rect(left, top, right, bottom)
+ }
}
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 00fb298ea1cc..43ce1668c4df 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -535,5 +535,7 @@
<!-- The vertical margin that needs to be preserved between the scaled window bounds and the
original window bounds (once the surface is scaled enough to do so) -->
<dimen name="cross_task_back_vertical_margin">8dp</dimen>
+ <!-- The offset from the left edge of the entering page for the cross-activity animation -->
+ <dimen name="cross_activity_back_entering_start_offset">96dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
deleted file mode 100644
index d6f7c367f772..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.back;
-
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.window.BackEvent.EDGE_RIGHT;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
-import static com.android.wm.shell.back.BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.RemoteException;
-import android.util.FloatProperty;
-import android.util.TypedValue;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.animation.Interpolator;
-import android.window.BackEvent;
-import android.window.BackMotionEvent;
-import android.window.BackProgressAnimator;
-import android.window.IOnBackInvokedCallback;
-
-import com.android.internal.dynamicanimation.animation.SpringAnimation;
-import com.android.internal.dynamicanimation.animation.SpringForce;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-
-import javax.inject.Inject;
-
-/** Class that defines cross-activity animation. */
-@ShellMainThread
-public class CrossActivityBackAnimation extends ShellBackAnimation {
- /**
- * Minimum scale of the entering/closing window.
- */
- private static final float MIN_WINDOW_SCALE = 0.9f;
-
- /** Duration of post animation after gesture committed. */
- private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE;
- private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
- new FloatProperty<>("enter-alpha") {
- @Override
- public void setValue(CrossActivityBackAnimation anim, float value) {
- anim.setEnteringProgress(value);
- }
-
- @Override
- public Float get(CrossActivityBackAnimation object) {
- return object.getEnteringProgress();
- }
- };
- private static final FloatProperty<CrossActivityBackAnimation> LEAVE_PROGRESS_PROP =
- new FloatProperty<>("leave-alpha") {
- @Override
- public void setValue(CrossActivityBackAnimation anim, float value) {
- anim.setLeavingProgress(value);
- }
-
- @Override
- public Float get(CrossActivityBackAnimation object) {
- return object.getLeavingProgress();
- }
- };
- private static final float MIN_WINDOW_ALPHA = 0.01f;
- private static final float WINDOW_X_SHIFT_DP = 48;
- private static final int SCALE_FACTOR = 100;
- // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
- private static final float TARGET_COMMIT_PROGRESS = 0.5f;
- private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
-
- private final Rect mStartTaskRect = new Rect();
- private final float mCornerRadius;
-
- // The closing window properties.
- private final RectF mClosingRect = new RectF();
-
- // The entering window properties.
- private final Rect mEnteringStartRect = new Rect();
- private final RectF mEnteringRect = new RectF();
- private final SpringAnimation mEnteringProgressSpring;
- private final SpringAnimation mLeavingProgressSpring;
- // Max window x-shift in pixels.
- private final float mWindowXShift;
- private final BackAnimationRunner mBackAnimationRunner;
-
- private float mEnteringProgress = 0f;
- private float mLeavingProgress = 0f;
-
- private final PointF mInitialTouchPos = new PointF();
-
- private final Matrix mTransformMatrix = new Matrix();
-
- private final float[] mTmpFloat9 = new float[9];
-
- private RemoteAnimationTarget mEnteringTarget;
- private RemoteAnimationTarget mClosingTarget;
- private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
- private boolean mBackInProgress = false;
- private boolean mIsRightEdge;
- private boolean mTriggerBack = false;
-
- private PointF mTouchPos = new PointF();
- private IRemoteAnimationFinishedCallback mFinishCallback;
-
- private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-
- private final BackAnimationBackground mBackground;
-
- @Inject
- public CrossActivityBackAnimation(Context context, BackAnimationBackground background) {
- mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(
- new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
- mBackground = background;
- mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
- mEnteringProgressSpring.setSpring(new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
- mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP);
- mLeavingProgressSpring.setSpring(new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
- mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP,
- context.getResources().getDisplayMetrics());
- }
-
- /**
- * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two.
- * From https://en.wikipedia.org/wiki/Smoothstep
- */
- private static float smoothstep(float edge0, float edge1, float x) {
- if (x < edge0) return 0;
- if (x >= edge1) return 1;
-
- x = (x - edge0) / (edge1 - edge0);
- return x * x * (3 - 2 * x);
- }
-
- /**
- * Linearly map x from range (a1, a2) to range (b1, b2).
- */
- private static float mapLinear(float x, float a1, float a2, float b1, float b2) {
- return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
- }
-
- /**
- * Linearly map a normalized value from (0, 1) to (min, max).
- */
- private static float mapRange(float value, float min, float max) {
- return min + (value * (max - min));
- }
-
- private void startBackAnimation() {
- if (mEnteringTarget == null || mClosingTarget == null) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
- return;
- }
- mTransaction.setAnimationTransaction();
-
- // Offset start rectangle to align task bounds.
- mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
- mStartTaskRect.offsetTo(0, 0);
-
- // Draw background with task background color.
- mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(),
- mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
- setEnteringProgress(0);
- setLeavingProgress(0);
- }
-
- private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
- if (leash == null || !leash.isValid()) {
- return;
- }
-
- final float scale = targetRect.width() / mStartTaskRect.width();
- mTransformMatrix.reset();
- mTransformMatrix.setScale(scale, scale);
- mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
- mTransaction.setAlpha(leash, targetAlpha)
- .setMatrix(leash, mTransformMatrix, mTmpFloat9)
- .setWindowCrop(leash, mStartTaskRect)
- .setCornerRadius(leash, mCornerRadius);
- }
-
- private void finishAnimation() {
- if (mEnteringTarget != null) {
- if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) {
- mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
- mEnteringTarget.leash.release();
- }
- mEnteringTarget = null;
- }
- if (mClosingTarget != null) {
- if (mClosingTarget.leash != null) {
- mClosingTarget.leash.release();
- }
- mClosingTarget = null;
- }
- if (mBackground != null) {
- mBackground.removeBackground(mTransaction);
- }
-
- mTransaction.apply();
- mBackInProgress = false;
- mTransformMatrix.reset();
- mInitialTouchPos.set(0, 0);
-
- if (mFinishCallback != null) {
- try {
- mFinishCallback.onAnimationFinished();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- mFinishCallback = null;
- }
- mEnteringProgressSpring.animateToFinalPosition(0);
- mEnteringProgressSpring.skipToEnd();
- mLeavingProgressSpring.animateToFinalPosition(0);
- mLeavingProgressSpring.skipToEnd();
- }
-
- private void onGestureProgress(@NonNull BackEvent backEvent) {
- if (!mBackInProgress) {
- mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
- mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
- mBackInProgress = true;
- }
- mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
-
- float progress = backEvent.getProgress();
- float springProgress = (mTriggerBack
- ? mapLinear(progress, 0f, 1, TARGET_COMMIT_PROGRESS, 1)
- : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
- mLeavingProgressSpring.animateToFinalPosition(springProgress);
- mEnteringProgressSpring.animateToFinalPosition(springProgress);
- mBackground.onBackProgressed(progress);
- }
-
- private void onGestureCommitted() {
- if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null
- || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid()
- || !mClosingTarget.leash.isValid()) {
- finishAnimation();
- return;
- }
- // End the fade animations
- mLeavingProgressSpring.cancel();
- mEnteringProgressSpring.cancel();
-
- // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
- // coordinate of the gesture driven phase.
- mEnteringRect.round(mEnteringStartRect);
- mTransaction.hide(mClosingTarget.leash);
-
- ValueAnimator valueAnimator =
- ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(INTERPOLATOR);
- valueAnimator.addUpdateListener(animation -> {
- float progress = animation.getAnimatedFraction();
- updatePostCommitEnteringAnimation(progress);
- if (progress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD) {
- mBackground.resetStatusBarCustomization();
- }
- mTransaction.apply();
- });
-
- valueAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBackground.resetStatusBarCustomization();
- finishAnimation();
- }
- });
- valueAnimator.start();
- }
-
- private void updatePostCommitEnteringAnimation(float progress) {
- float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
- float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
- float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
- float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f);
- mEnteringRect.set(left, top, left + width, top + height);
- applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
- }
-
- private float getPreCommitEnteringAlpha() {
- return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
- MIN_WINDOW_ALPHA);
- }
-
- private float getEnteringProgress() {
- return mEnteringProgress * SCALE_FACTOR;
- }
-
- private void setEnteringProgress(float value) {
- mEnteringProgress = value / SCALE_FACTOR;
- if (mEnteringTarget != null && mEnteringTarget.leash != null) {
- transformWithProgress(
- mEnteringProgress,
- getPreCommitEnteringAlpha(),
- mEnteringTarget.leash,
- mEnteringRect,
- -mWindowXShift,
- 0
- );
- }
- }
-
- private float getPreCommitLeavingAlpha() {
- return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
- MIN_WINDOW_ALPHA);
- }
-
- private float getLeavingProgress() {
- return mLeavingProgress * SCALE_FACTOR;
- }
-
- private void setLeavingProgress(float value) {
- mLeavingProgress = value / SCALE_FACTOR;
- if (mClosingTarget != null && mClosingTarget.leash != null) {
- transformWithProgress(
- mLeavingProgress,
- getPreCommitLeavingAlpha(),
- mClosingTarget.leash,
- mClosingRect,
- 0,
- mIsRightEdge ? 0 : mWindowXShift
- );
- }
- }
-
- private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
- RectF targetRect, float deltaXMin, float deltaXMax) {
-
- final int width = mStartTaskRect.width();
- final int height = mStartTaskRect.height();
-
- final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress);
- final float closingScale = MIN_WINDOW_SCALE
- + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE);
- final float closingWidth = closingScale * width;
- final float closingHeight = (float) height / width * closingWidth;
-
- // Move the window along the X axis.
- float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
- closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
-
- // Move the window along the Y axis.
- final float closingTop = (height - closingHeight) * 0.5f;
- targetRect.set(
- closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
-
- applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA));
- mTransaction.apply();
- }
-
- @Override
- public BackAnimationRunner getRunner() {
- return mBackAnimationRunner;
- }
-
- private final class Callback extends IOnBackInvokedCallback.Default {
- @Override
- public void onBackStarted(BackMotionEvent backEvent) {
- mTriggerBack = backEvent.getTriggerBack();
- mProgressAnimator.onBackStarted(backEvent,
- CrossActivityBackAnimation.this::onGestureProgress);
- }
-
- @Override
- public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
- mTriggerBack = backEvent.getTriggerBack();
- mProgressAnimator.onBackProgressed(backEvent);
- }
-
- @Override
- public void onBackCancelled() {
- mProgressAnimator.onBackCancelled(() -> {
- // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
- // and if we release all animation leash first, the leavingProgressSpring won't
- // able to update the animation anymore, which cause flicker.
- // Here should force update the closing animation target to the final stage before
- // release it.
- setLeavingProgress(0);
- finishAnimation();
- });
- }
-
- @Override
- public void onBackInvoked() {
- mProgressAnimator.reset();
- onGestureCommitted();
- }
- }
-
- private final class Runner extends IRemoteAnimationRunner.Default {
- @Override
- public void onAnimationStart(
- int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
- for (RemoteAnimationTarget a : apps) {
- if (a.mode == MODE_CLOSING) {
- mClosingTarget = a;
- }
- if (a.mode == MODE_OPENING) {
- mEnteringTarget = a;
- }
- }
-
- startBackAnimation();
- mFinishCallback = finishedCallback;
- }
-
- @Override
- public void onAnimationCancelled() {
- finishAnimation();
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
new file mode 100644
index 000000000000..edf29dd484fc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.back
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Matrix
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.os.RemoteException
+import android.view.Display
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.Interpolator
+import android.window.BackEvent
+import android.window.BackMotionEvent
+import android.window.BackProgressAnimator
+import android.window.IOnBackInvokedCallback
+import com.android.internal.jank.Cuj
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.common.annotations.ShellMainThread
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityBackAnimation @Inject constructor(
+ private val context: Context,
+ private val background: BackAnimationBackground,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+) : ShellBackAnimation() {
+
+ private val startClosingRect = RectF()
+ private val targetClosingRect = RectF()
+ private val currentClosingRect = RectF()
+
+ private val startEnteringRect = RectF()
+ private val targetEnteringRect = RectF()
+ private val currentEnteringRect = RectF()
+
+ private val taskBoundsRect = Rect()
+
+ private val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+
+ private val backAnimationRunner = BackAnimationRunner(
+ Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY
+ )
+ private val initialTouchPos = PointF()
+ private val transformMatrix = Matrix()
+ private val tmpFloat9 = FloatArray(9)
+ private var enteringTarget: RemoteAnimationTarget? = null
+ private var closingTarget: RemoteAnimationTarget? = null
+ private val transaction = SurfaceControl.Transaction()
+ private var triggerBack = false
+ private var finishCallback: IRemoteAnimationFinishedCallback? = null
+ private val progressAnimator = BackProgressAnimator()
+ private val displayBoundsMargin =
+ context.resources.getDimension(R.dimen.cross_task_back_vertical_margin)
+ private val enteringStartOffset =
+ context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
+
+ private val gestureInterpolator = Interpolators.STANDARD_DECELERATE
+ private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+ private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator()
+
+ private var scrimLayer: SurfaceControl? = null
+ private var maxScrimAlpha: Float = 0f
+
+ override fun getRunner() = backAnimationRunner
+
+ private fun startBackAnimation(backMotionEvent: BackMotionEvent) {
+ if (enteringTarget == null || closingTarget == null) {
+ ProtoLog.d(
+ ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
+ "Entering target or closing target is null."
+ )
+ return
+ }
+ triggerBack = backMotionEvent.triggerBack
+ initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY)
+
+ transaction.setAnimationTransaction()
+
+ // Offset start rectangle to align task bounds.
+ taskBoundsRect.set(closingTarget!!.windowConfiguration.bounds)
+ taskBoundsRect.offsetTo(0, 0)
+
+ startClosingRect.set(taskBoundsRect)
+
+ // scale closing target into the middle for rhs and to the right for lhs
+ targetClosingRect.set(startClosingRect)
+ targetClosingRect.scaleCentered(MAX_SCALE)
+ if (backMotionEvent.swipeEdge != BackEvent.EDGE_RIGHT) {
+ targetClosingRect.offset(
+ startClosingRect.right - targetClosingRect.right - displayBoundsMargin, 0f
+ )
+ }
+
+ // the entering target starts 96dp to the left of the screen edge...
+ startEnteringRect.set(startClosingRect)
+ startEnteringRect.offset(-enteringStartOffset, 0f)
+
+ // ...and gets scaled in sync with the closing target
+ targetEnteringRect.set(startEnteringRect)
+ targetEnteringRect.scaleCentered(MAX_SCALE)
+
+ // Draw background with task background color.
+ background.ensureBackground(
+ closingTarget!!.windowConfiguration.bounds,
+ enteringTarget!!.taskInfo.taskDescription!!.backgroundColor, transaction
+ )
+ ensureScrimLayer()
+ transaction.apply()
+ }
+
+ private fun onGestureProgress(backEvent: BackEvent) {
+ val progress = gestureInterpolator.getInterpolation(backEvent.progress)
+ background.onBackProgressed(progress)
+ currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
+ val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
+ currentClosingRect.offset(0f, yOffset)
+ applyTransform(closingTarget?.leash, currentClosingRect, 1f)
+ currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
+ currentEnteringRect.offset(0f, yOffset)
+ applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ transaction.apply()
+ }
+
+ private fun getYOffset(centeredRect: RectF, touchY: Float): Float {
+ val screenHeight = taskBoundsRect.height()
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ val rawYDelta = touchY - initialTouchPos.y
+ val yDirection = (if (rawYDelta < 0) -1 else 1)
+ // limit yDelta interpretation to 1/2 of screen height in either direction
+ val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f)
+ val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio)
+ // limit y-shift so surface never passes 8dp screen margin
+ val deltaY = yDirection * interpolatedYRatio * max(
+ 0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin
+ )
+ return deltaY
+ }
+
+ private fun onGestureCommitted() {
+ if (closingTarget?.leash == null || enteringTarget?.leash == null ||
+ !enteringTarget!!.leash.isValid || !closingTarget!!.leash.isValid
+ ) {
+ finishAnimation()
+ return
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase. Let's update the start and target rects and kick
+ // off the animator
+ startClosingRect.set(currentClosingRect)
+ startEnteringRect.set(currentEnteringRect)
+ targetEnteringRect.set(taskBoundsRect)
+ targetClosingRect.set(taskBoundsRect)
+ targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f)
+
+ val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION)
+ valueAnimator.addUpdateListener { animation: ValueAnimator ->
+ val progress = animation.animatedFraction
+ onPostCommitProgress(progress)
+ if (progress > 1 - BackAnimationConstants.UPDATE_SYSUI_FLAGS_THRESHOLD) {
+ background.resetStatusBarCustomization()
+ }
+ }
+ valueAnimator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ background.resetStatusBarCustomization()
+ finishAnimation()
+ }
+ })
+ valueAnimator.start()
+ }
+
+ private fun onPostCommitProgress(linearProgress: Float) {
+ val closingAlpha = max(1f - linearProgress * 2, 0f)
+ val progress = postCommitInterpolator.getInterpolation(linearProgress)
+ scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) }
+ currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
+ applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+ currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
+ applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ transaction.apply()
+ }
+
+ private fun finishAnimation() {
+ enteringTarget?.let {
+ if (it.leash != null && it.leash.isValid) {
+ transaction.setCornerRadius(it.leash, 0f)
+ it.leash.release()
+ }
+ enteringTarget = null
+ }
+
+ closingTarget?.leash?.release()
+ closingTarget = null
+
+ background.removeBackground(transaction)
+ transaction.apply()
+ transformMatrix.reset()
+ initialTouchPos.set(0f, 0f)
+ try {
+ finishCallback?.onAnimationFinished()
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ finishCallback = null
+ removeScrimLayer()
+ }
+
+ private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) {
+ if (leash == null || !leash.isValid) return
+ val scale = rect.width() / taskBoundsRect.width()
+ transformMatrix.reset()
+ transformMatrix.setScale(scale, scale)
+ transformMatrix.postTranslate(rect.left, rect.top)
+ transaction.setAlpha(leash, alpha)
+ .setMatrix(leash, transformMatrix, tmpFloat9)
+ .setCrop(leash, taskBoundsRect)
+ .setCornerRadius(leash, cornerRadius)
+ }
+
+ private fun ensureScrimLayer() {
+ if (scrimLayer != null) return
+ val isDarkTheme: Boolean = isDarkMode(context)
+ val scrimBuilder = SurfaceControl.Builder()
+ .setName("Cross-Activity back animation scrim")
+ .setCallsite("CrossActivityBackAnimation")
+ .setColorLayer()
+ .setOpaque(false)
+ .setHidden(false)
+
+ rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder)
+ scrimLayer = scrimBuilder.build()
+ val colorComponents = floatArrayOf(0f, 0f, 0f)
+ maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT
+ transaction
+ .setColor(scrimLayer, colorComponents)
+ .setAlpha(scrimLayer!!, maxScrimAlpha)
+ .setRelativeLayer(scrimLayer!!, closingTarget!!.leash, -1)
+ .show(scrimLayer)
+ }
+
+ private fun removeScrimLayer() {
+ scrimLayer?.let {
+ if (it.isValid) {
+ transaction.remove(it).apply()
+ }
+ }
+ scrimLayer = null
+ }
+
+
+ private inner class Callback : IOnBackInvokedCallback.Default() {
+ override fun onBackStarted(backMotionEvent: BackMotionEvent) {
+ startBackAnimation(backMotionEvent)
+ progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent ->
+ onGestureProgress(backEvent)
+ }
+ }
+
+ override fun onBackProgressed(backEvent: BackMotionEvent) {
+ triggerBack = backEvent.triggerBack
+ progressAnimator.onBackProgressed(backEvent)
+ }
+
+ override fun onBackCancelled() {
+ progressAnimator.onBackCancelled {
+ finishAnimation()
+ }
+ }
+
+ override fun onBackInvoked() {
+ progressAnimator.reset()
+ onGestureCommitted()
+ }
+ }
+
+ private inner class Runner : IRemoteAnimationRunner.Default() {
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>?,
+ nonApps: Array<RemoteAnimationTarget>?,
+ finishedCallback: IRemoteAnimationFinishedCallback
+ ) {
+ ProtoLog.d(
+ ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "Start back to activity animation."
+ )
+ for (a in apps) {
+ when (a.mode) {
+ RemoteAnimationTarget.MODE_CLOSING -> closingTarget = a
+ RemoteAnimationTarget.MODE_OPENING -> enteringTarget = a
+ }
+ }
+ finishCallback = finishedCallback
+ }
+
+ override fun onAnimationCancelled() {
+ finishAnimation()
+ }
+ }
+
+ companion object {
+ /** Max scale of the entering/closing window.*/
+ private const val MAX_SCALE = 0.9f
+
+ /** Duration of post animation after gesture committed. */
+ private const val POST_ANIMATION_DURATION = 300L
+
+ private const val MAX_SCRIM_ALPHA_DARK = 0.8f
+ private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
+ }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+ return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES
+}
+
+private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) {
+ require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" }
+ left = start.left + (target.left - start.left) * progress
+ top = start.top + (target.top - start.top) * progress
+ right = start.right + (target.right - start.right) * progress
+ bottom = start.bottom + (target.bottom - start.bottom) * progress
+}
+
+private fun RectF.scaleCentered(
+ scale: Float,
+ pivotX: Float = left + width() / 2,
+ pivotY: Float = top + height() / 2
+) {
+ offset(-pivotX, -pivotY) // move pivot to origin
+ scale(scale)
+ offset(pivotX, pivotY) // Move back to the original position
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index f4a401c64a31..4d5e516f76e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -870,7 +870,7 @@ public class BubblePositioner {
if (onLeft) {
left = getInsets().left + padding;
} else {
- left = getAvailableRect().width() - width - padding;
+ left = getAvailableRect().right - width - padding;
}
int top = getExpandedViewBottomForBubbleBar() - height;
out.offsetTo(left, top);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 1c54754e9953..370720746808 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -332,6 +332,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+ int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+
// Pull out the pairs as we iterate back in the list
ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
for (int i = 0; i < rawList.size(); i++) {
@@ -344,6 +346,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
+ if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
+ mostRecentFreeformTaskIndex = recentTasks.size();
+ }
freeformTasks.add(taskInfo);
continue;
}
@@ -362,7 +367,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
- recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks(
+ recentTasks.add(mostRecentFreeformTaskIndex, GroupedRecentTaskInfo.forFreeformTasks(
freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 9ded6ea1d187..703eb199f260 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -61,6 +61,7 @@ import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -113,6 +114,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
private InputManager mInputManager;
@Mock
private ShellCommandHandler mShellCommandHandler;
+ @Mock
+ private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
@@ -133,7 +136,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellInit = spy(new ShellInit(mShellExecutor));
mShellBackAnimationRegistry =
new ShellBackAnimationRegistry(
- new CrossActivityBackAnimation(mContext, mAnimationBackground),
+ new CrossActivityBackAnimation(
+ mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer),
new CrossTaskBackAnimation(mContext, mAnimationBackground),
/* dialogCloseAnimation= */ null,
new CustomizeActivityAnimation(mContext, mAnimationBackground),
@@ -528,8 +532,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void testBackToActivity() throws RemoteException {
- final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(mContext,
- mAnimationBackground);
+ final CrossActivityBackAnimation animation = new CrossActivityBackAnimation(
+ mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer);
verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 41a4e8d503c9..d38e97f378c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -302,6 +302,54 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
+
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ setRawList(t1, t2, t3, t4, t5);
+
+ SplitBounds pair1Bounds =
+ new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50);
+ mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
+
+ when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
+ assertEquals(3, recentTasks.size());
+ GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
+ GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
+ GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+
+ // Check that groups have expected types
+ assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+
+ // Check freeform group entries
+ assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
+ assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+
+ // Check split group entries
+ assertEquals(t1, splitGroup.getTaskInfoList().get(0));
+ assertEquals(t2, splitGroup.getTaskInfoList().get(1));
+
+ // Check single entry
+ assertEquals(t4, singleGroup.getTaskInfo1());
+
+ mockitoSession.finishMocking();
+ }
+
+ @Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 7f09dd5d07cc..914987ac4650 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -33,10 +33,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
android:src="@drawable/more_horiz_24px"
android:tint="?androidprv:attr/materialColorOnSurface"
- android:layout_alignParentStart="true"
android:contentDescription="@string/more_options_content_description"
android:background="@null"/>
@@ -44,8 +43,8 @@
android:id="@+id/text_container"
android:layout_width="@dimen/autofill_dropdown_textview_max_width"
android:layout_height="wrap_content"
- android:paddingLeft="@dimen/autofill_view_left_padding"
- android:paddingRight="@dimen/autofill_view_right_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
+ android:paddingEnd="@dimen/autofill_view_right_padding"
android:paddingTop="@dimen/more_options_item_vertical_padding"
android:paddingBottom="@dimen/more_options_item_vertical_padding"
android:orientation="vertical">
@@ -54,9 +53,7 @@
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
android:textColor="?androidprv:attr/materialColorOnSurface"
- android:layout_toEndOf="@android:id/icon1"
style="@style/autofill.TextTitle"/>
</LinearLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 08948d793488..e998fe8fc8d9 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -42,8 +42,8 @@
android:id="@+id/text_container"
android:layout_width="@dimen/autofill_dropdown_textview_max_width"
android:layout_height="wrap_content"
- android:paddingLeft="@dimen/autofill_view_left_padding"
- android:paddingRight="@dimen/autofill_view_right_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
+ android:paddingEnd="@dimen/autofill_view_right_padding"
android:paddingTop="@dimen/autofill_view_top_padding"
android:paddingBottom="@dimen/autofill_view_bottom_padding"
android:orientation="vertical">
@@ -52,8 +52,6 @@
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@android:id/icon1"
android:textColor="?androidprv:attr/materialColorOnSurface"
style="@style/autofill.TextTitle"/>
@@ -61,8 +59,6 @@
android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@android:id/text1"
- android:layout_toEndOf="@android:id/icon1"
android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
style="@style/autofill.TextSubtitle"/>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 215ead367148..167d50614783 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -108,18 +108,19 @@ public class InstallSuccess extends Activity {
mDialog = builder.create();
mDialog.show();
mDialog.requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
- // Enable or disable "launch" button
- boolean enabled = false;
+ // Show or hide "launch" button
+ boolean visible = false;
if (mLaunchIntent != null) {
List<ResolveInfo> list = getPackageManager().queryIntentActivities(mLaunchIntent,
0);
if (list != null && list.size() > 0) {
- enabled = true;
+ visible = true;
}
}
+ visible = visible && isLauncherActivityEnabled(mLaunchIntent);
Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
- if (enabled) {
+ if (visible) {
launchButton.setOnClickListener(view -> {
try {
startActivity(mLaunchIntent.addFlags(
@@ -130,7 +131,15 @@ public class InstallSuccess extends Activity {
finish();
});
} else {
- launchButton.setEnabled(false);
+ launchButton.setVisibility(View.GONE);
}
}
+
+ private boolean isLauncherActivityEnabled(Intent intent) {
+ if (intent == null || intent.getComponent() == null) {
+ return false;
+ }
+ return getPackageManager().getComponentEnabledSetting(intent.getComponent())
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index aeabbd53d177..32795e4b2f1c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -30,6 +30,7 @@ import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
import android.content.pm.PackageInstaller.SessionParams
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.os.Process
@@ -830,7 +831,8 @@ class InstallRepository(private val context: Context) {
val resultIntent = if (shouldReturnResult) {
Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
} else {
- packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+ val intent = packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+ if (isLauncherActivityEnabled(intent)) intent else null
}
_installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
} else {
@@ -838,6 +840,14 @@ class InstallRepository(private val context: Context) {
}
}
+ private fun isLauncherActivityEnabled(intent: Intent?): Boolean {
+ if (intent == null || intent.component == null) {
+ return false
+ }
+ return (intent.component?.let { packageManager.getComponentEnabledSetting(it) }
+ != COMPONENT_ENABLED_STATE_DISABLED)
+ }
+
/**
* Cleanup the staged session. Also signal the packageinstaller that an install session is to
* be aborted
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
index b2a65faa0a91..e491f9c87313 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -23,13 +23,13 @@ import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.InstallStage;
import com.android.packageinstaller.v2.model.InstallSuccess;
import com.android.packageinstaller.v2.ui.InstallActionListener;
import java.util.List;
@@ -40,6 +40,7 @@ import java.util.List;
*/
public class InstallSuccessFragment extends DialogFragment {
+ private static final String LOG_TAG = InstallSuccessFragment.class.getSimpleName();
private final InstallSuccess mDialogData;
private AlertDialog mDialog;
private InstallActionListener mInstallActionListener;
@@ -60,12 +61,15 @@ public class InstallSuccessFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
- mDialog = new AlertDialog.Builder(requireContext()).setTitle(mDialogData.getAppLabel())
- .setIcon(mDialogData.getAppIcon()).setView(dialogView).setNegativeButton(R.string.done,
+ mDialog = new AlertDialog.Builder(requireContext())
+ .setTitle(mDialogData.getAppLabel())
+ .setIcon(mDialogData.getAppIcon())
+ .setView(dialogView)
+ .setNegativeButton(R.string.done,
(dialog, which) -> mInstallActionListener.onNegativeResponse(
- InstallStage.STAGE_SUCCESS))
- .setPositiveButton(R.string.launch, (dialog, which) -> {
- }).create();
+ mDialogData.getStageCode()))
+ .setPositiveButton(R.string.launch, (dialog, which) -> {})
+ .create();
dialogView.requireViewById(R.id.install_success).setVisibility(View.VISIBLE);
@@ -76,25 +80,28 @@ public class InstallSuccessFragment extends DialogFragment {
public void onStart() {
super.onStart();
Button launchButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
- boolean enabled = false;
+ boolean visible = false;
if (mDialogData.getResultIntent() != null) {
List<ResolveInfo> list = mPm.queryIntentActivities(mDialogData.getResultIntent(), 0);
if (list.size() > 0) {
- enabled = true;
+ visible = true;
}
}
- if (enabled) {
+ if (visible) {
launchButton.setOnClickListener(view -> {
+ Log.i(LOG_TAG, "Finished installing and launching " +
+ mDialogData.getAppLabel());
mInstallActionListener.openInstalledApp(mDialogData.getResultIntent());
});
} else {
- launchButton.setEnabled(false);
+ launchButton.setVisibility(View.GONE);
}
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
+ Log.i(LOG_TAG, "Finished installing " + mDialogData.getAppLabel());
mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
}
}
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
new file mode 100644
index 000000000000..b52586c2d8d9
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.datastore
+
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class KeyedObserverTest {
+ @get:Rule
+ val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var observer1: KeyedObserver<Any?>
+
+ @Mock
+ private lateinit var observer2: KeyedObserver<Any?>
+
+ @Mock
+ private lateinit var keyedObserver1: KeyedObserver<Any>
+
+ @Mock
+ private lateinit var keyedObserver2: KeyedObserver<Any>
+
+ @Mock
+ private lateinit var key1: Any
+
+ @Mock
+ private lateinit var key2: Any
+
+ @Mock
+ private lateinit var executor: Executor
+
+ private val keyedObservable = KeyedDataObservable<Any>()
+
+ @Test
+ fun addObserver_sameExecutor() {
+ keyedObservable.addObserver(observer1, executor)
+ keyedObservable.addObserver(observer1, executor)
+ }
+
+ @Test
+ fun addObserver_keyedObserver_sameExecutor() {
+ keyedObservable.addObserver(key1, keyedObserver1, executor)
+ keyedObservable.addObserver(key1, keyedObserver1, executor)
+ }
+
+ @Test
+ fun addObserver_differentExecutor() {
+ keyedObservable.addObserver(observer1, executor)
+ Assert.assertThrows(IllegalStateException::class.java) {
+ keyedObservable.addObserver(observer1, directExecutor())
+ }
+ }
+
+ @Test
+ fun addObserver_keyedObserver_differentExecutor() {
+ keyedObservable.addObserver(key1, keyedObserver1, executor)
+ Assert.assertThrows(IllegalStateException::class.java) {
+ keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
+ }
+ }
+
+ @Test
+ fun addObserver_weaklyReferenced() {
+ val counter = AtomicInteger()
+ var observer: KeyedObserver<Any?>? = KeyedObserver { _, _ -> counter.incrementAndGet() }
+ keyedObservable.addObserver(observer!!, directExecutor())
+
+ keyedObservable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+
+ // trigger GC, the observer callback should not be invoked
+ null.also { observer = it }
+ System.gc()
+ System.runFinalization()
+
+ keyedObservable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+ }
+
+ @Test
+ fun addObserver_keyedObserver_weaklyReferenced() {
+ val counter = AtomicInteger()
+ var keyObserver: KeyedObserver<Any>? = KeyedObserver { _, _ -> counter.incrementAndGet() }
+ keyedObservable.addObserver(key1, keyObserver!!, directExecutor())
+
+ keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+
+ // trigger GC, the observer callback should not be invoked
+ null.also { keyObserver = it }
+ System.gc()
+ System.runFinalization()
+
+ keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+ }
+
+ @Test
+ fun addObserver_notifyObservers_removeObserver() {
+ keyedObservable.addObserver(observer1, directExecutor())
+ keyedObservable.addObserver(observer2, executor)
+
+ keyedObservable.notifyChange(ChangeReason.UPDATE)
+ verify(observer1).onKeyChanged(null, ChangeReason.UPDATE)
+ verify(observer2, never()).onKeyChanged(any(), any())
+ verify(executor).execute(any())
+
+ reset(observer1, executor)
+ keyedObservable.removeObserver(observer2)
+
+ keyedObservable.notifyChange(ChangeReason.DELETE)
+ verify(observer1).onKeyChanged(null, ChangeReason.DELETE)
+ verify(executor, never()).execute(any())
+ }
+
+ @Test
+ fun addObserver_keyedObserver_notifyObservers_removeObserver() {
+ keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
+ keyedObservable.addObserver(key2, keyedObserver2, executor)
+
+ keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
+ verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE)
+ verify(keyedObserver2, never()).onKeyChanged(any(), any())
+ verify(executor, never()).execute(any())
+
+ reset(keyedObserver1, executor)
+ keyedObservable.removeObserver(key2, keyedObserver2)
+
+ keyedObservable.notifyChange(key1, ChangeReason.DELETE)
+ verify(keyedObserver1).onKeyChanged(key1, ChangeReason.DELETE)
+ verify(executor, never()).execute(any())
+ }
+
+ @Test
+ fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() {
+ keyedObservable.addObserver(observer1, directExecutor())
+ keyedObservable.addObserver(key1, keyedObserver1, directExecutor())
+ keyedObservable.addObserver(key2, keyedObserver2, directExecutor())
+
+ keyedObservable.notifyChange(ChangeReason.UPDATE)
+ verify(observer1).onKeyChanged(null, ChangeReason.UPDATE)
+ verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE)
+ verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE)
+
+ reset(observer1, keyedObserver1, keyedObserver2)
+ keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
+
+ verify(observer1).onKeyChanged(key1, ChangeReason.UPDATE)
+ verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE)
+ verify(keyedObserver2, never()).onKeyChanged(key1, ChangeReason.UPDATE)
+
+ reset(observer1, keyedObserver1, keyedObserver2)
+ keyedObservable.notifyChange(key2, ChangeReason.UPDATE)
+
+ verify(observer1).onKeyChanged(key2, ChangeReason.UPDATE)
+ verify(keyedObserver1, never()).onKeyChanged(key2, ChangeReason.UPDATE)
+ verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE)
+ }
+
+ @Test
+ fun notifyChange_addObserverWithinCallback() {
+ // ConcurrentModificationException is raised if it is not implemented correctly
+ val observer: KeyedObserver<Any?> = KeyedObserver { _, _ ->
+ keyedObservable.addObserver(observer1, executor)
+ }
+
+ keyedObservable.addObserver(observer, directExecutor())
+
+ keyedObservable.notifyChange(ChangeReason.UPDATE)
+ keyedObservable.removeObserver(observer)
+ }
+
+ @Test
+ fun notifyChange_KeyedObserver_addObserverWithinCallback() {
+ // ConcurrentModificationException is raised if it is not implemented correctly
+ val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ ->
+ keyedObservable.addObserver(key1, keyedObserver1, executor)
+ }
+
+ keyedObservable.addObserver(key1, keyObserver, directExecutor())
+
+ keyedObservable.notifyChange(key1, ChangeReason.UPDATE)
+ keyedObservable.removeObserver(key1, keyObserver)
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
index bb791dc9a23c..f0658290beb0 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -69,8 +69,7 @@ class ObserverTest {
assertThat(counter.get()).isEqualTo(1)
// trigger GC, the observer callback should not be invoked
- @Suppress("unused")
- observer = null
+ null.also { observer = it }
System.gc()
System.runFinalization()
@@ -100,10 +99,12 @@ class ObserverTest {
@Test
fun notifyChange_addObserverWithinCallback() {
// ConcurrentModificationException is raised if it is not implemented correctly
+ val observer = Observer { observable.addObserver(observer1, executor) }
observable.addObserver(
- { observable.addObserver(observer1, executor) },
+ observer,
MoreExecutors.directExecutor()
)
observable.notifyChange(ChangeReason.UPDATE)
+ observable.removeObserver(observer)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index b8624fd9605b..4777b0de0732 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1315,8 +1315,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
boolean isActiveAshaHearingAid = mIsActiveDeviceHearingAid;
boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
&& isConnectedHapClientDevice();
- if ((isActiveAshaHearingAid || isActiveLeAudioHearingAid)
- && stringRes == R.string.bluetooth_active_no_battery_level) {
+ if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) {
final Set<CachedBluetoothDevice> memberDevices = getMemberDevice();
final CachedBluetoothDevice subDevice = getSubDevice();
if (memberDevices.stream().anyMatch(m -> m.isConnected())) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt
index cda6b8bb36be..68f471dd4e4f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.media.session
import android.media.session.MediaController
+import android.media.session.MediaSession
import android.media.session.MediaSessionManager
import android.os.UserHandle
import androidx.concurrent.futures.DirectExecutor
@@ -28,7 +29,7 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
/** [Flow] for [MediaSessionManager.OnActiveSessionsChangedListener]. */
-val MediaSessionManager.activeMediaChanges: Flow<Collection<MediaController>?>
+val MediaSessionManager.activeMediaChanges: Flow<List<MediaController>?>
get() =
callbackFlow {
val listener =
@@ -42,3 +43,24 @@ val MediaSessionManager.activeMediaChanges: Flow<Collection<MediaController>?>
awaitClose { removeOnActiveSessionsChangedListener(listener) }
}
.buffer(capacity = Channel.CONFLATED)
+
+/** [Flow] for [MediaSessionManager.RemoteSessionCallback]. */
+val MediaSessionManager.remoteSessionChanges: Flow<MediaSession.Token?>
+ get() =
+ callbackFlow {
+ val callback =
+ object : MediaSessionManager.RemoteSessionCallback {
+ override fun onVolumeChanged(sessionToken: MediaSession.Token, flags: Int) {
+ launch { send(sessionToken) }
+ }
+
+ override fun onDefaultRemoteSessionChanged(
+ sessionToken: MediaSession.Token?
+ ) {
+ launch { send(sessionToken) }
+ }
+ }
+ registerRemoteSessionCallback(DirectExecutor.INSTANCE, callback)
+ awaitClose { unregisterRemoteSessionCallback(callback) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 298dd71e555e..724dd51b8fe4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -15,14 +15,10 @@
*/
package com.android.settingslib.volume.data.repository
-import android.media.MediaRouter2Manager
-import android.media.RoutingSessionInfo
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
-import com.android.settingslib.volume.data.model.RoutingSession
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.model.AudioManagerEvent
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -30,35 +26,23 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
/** Repository providing data about connected media devices. */
interface LocalMediaRepository {
- /** Available devices list */
- val mediaDevices: StateFlow<Collection<MediaDevice>>
-
/** Currently connected media device */
val currentConnectedDevice: StateFlow<MediaDevice?>
-
- val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
-
- suspend fun adjustSessionVolume(sessionId: String?, volume: Int)
}
class LocalMediaRepositoryImpl(
audioManagerEventsReceiver: AudioManagerEventsReceiver,
private val localMediaManager: LocalMediaManager,
- private val mediaRouter2Manager: MediaRouter2Manager,
coroutineScope: CoroutineScope,
- private val backgroundContext: CoroutineContext,
) : LocalMediaRepository {
private val devicesChanges =
@@ -94,18 +78,6 @@ class LocalMediaRepositoryImpl(
}
.shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
- override val mediaDevices: StateFlow<Collection<MediaDevice>> =
- mediaDevicesUpdates
- .mapNotNull {
- if (it is DevicesUpdate.DeviceListUpdate) {
- it.newDevices ?: emptyList()
- } else {
- null
- }
- }
- .flowOn(backgroundContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
-
override val currentConnectedDevice: StateFlow<MediaDevice?> =
merge(devicesChanges, mediaDevicesUpdates)
.map { localMediaManager.currentConnectedDevice }
@@ -116,30 +88,6 @@ class LocalMediaRepositoryImpl(
localMediaManager.currentConnectedDevice
)
- override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>> =
- merge(devicesChanges, mediaDevicesUpdates)
- .onStart { emit(Unit) }
- .map { localMediaManager.remoteRoutingSessions.map(::toRoutingSession) }
- .flowOn(backgroundContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
-
- override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
- withContext(backgroundContext) {
- if (sessionId == null) {
- localMediaManager.adjustSessionVolume(volume)
- } else {
- localMediaManager.adjustSessionVolume(sessionId, volume)
- }
- }
- }
-
- private fun toRoutingSession(info: RoutingSessionInfo): RoutingSession =
- RoutingSession(
- info,
- isMediaOutputDisabled = mediaRouter2Manager.getTransferableRoutes(info).isEmpty(),
- isVolumeSeekBarEnabled = localMediaManager.shouldEnableVolumeSeekBar(info)
- )
-
private sealed interface DevicesUpdate {
data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index 7c231d1fad4e..e4ac9fe686a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -27,18 +27,26 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** Provides controllers for currently active device media sessions. */
interface MediaControllerRepository {
- /** Current [MediaController]. Null is emitted when there is no active [MediaController]. */
- val activeLocalMediaController: StateFlow<MediaController?>
+ /**
+ * Get a list of controllers for all ongoing sessions. The controllers will be provided in
+ * priority order with the most important controller at index 0.
+ *
+ * This requires the [android.Manifest.permission.MEDIA_CONTENT_CONTROL] permission be held by
+ * the calling app.
+ */
+ val activeSessions: StateFlow<List<MediaController>>
}
class MediaControllerRepositoryImpl(
@@ -49,51 +57,17 @@ class MediaControllerRepositoryImpl(
backgroundContext: CoroutineContext,
) : MediaControllerRepository {
- private val devicesChanges =
- audioManagerEventsReceiver.events.filterIsInstance(
- AudioManagerEvent.StreamDevicesChanged::class
- )
-
- override val activeLocalMediaController: StateFlow<MediaController?> =
- combine(
- mediaSessionManager.activeMediaChanges.onStart {
- emit(mediaSessionManager.getActiveSessions(null))
- },
- localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
- ?: flowOf(null),
- devicesChanges.onStart { emit(AudioManagerEvent.StreamDevicesChanged) },
- ) { controllers, _, _ ->
- controllers?.let(::findLocalMediaController)
- }
+ override val activeSessions: StateFlow<List<MediaController>> =
+ merge(
+ mediaSessionManager.activeMediaChanges.filterNotNull(),
+ localBluetoothManager?.headsetAudioModeChanges?.map {
+ mediaSessionManager.getActiveSessions(null)
+ } ?: emptyFlow(),
+ audioManagerEventsReceiver.events
+ .filterIsInstance(AudioManagerEvent.StreamDevicesChanged::class)
+ .map { mediaSessionManager.getActiveSessions(null) },
+ )
+ .onStart { emit(mediaSessionManager.getActiveSessions(null)) }
.flowOn(backgroundContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
-
- private fun findLocalMediaController(
- controllers: Collection<MediaController>,
- ): MediaController? {
- var localController: MediaController? = null
- val remoteMediaSessionLists: MutableList<String> = ArrayList()
- for (controller in controllers) {
- val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
- when (playbackInfo.playbackType) {
- MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
- if (localController?.packageName.equals(controller.packageName)) {
- localController = null
- }
- if (!remoteMediaSessionLists.contains(controller.packageName)) {
- remoteMediaSessionLists.add(controller.packageName)
- }
- }
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
- if (
- localController == null &&
- !remoteMediaSessionLists.contains(controller.packageName)
- ) {
- localController = controller
- }
- }
- }
- }
- return localController
- }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
deleted file mode 100644
index f6213351ae0d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
+++ /dev/null
@@ -1,57 +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.settingslib.volume.domain.interactor
-
-import com.android.settingslib.media.MediaDevice
-import com.android.settingslib.volume.data.repository.LocalMediaRepository
-import com.android.settingslib.volume.domain.model.RoutingSession
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-class LocalMediaInteractor(
- private val repository: LocalMediaRepository,
- coroutineScope: CoroutineScope,
-) {
-
- /** Available devices list */
- val mediaDevices: StateFlow<Collection<MediaDevice>>
- get() = repository.mediaDevices
-
- /** Currently connected media device */
- val currentConnectedDevice: StateFlow<MediaDevice?>
- get() = repository.currentConnectedDevice
-
- val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
- repository.remoteRoutingSessions
- .map { sessions ->
- sessions.map {
- RoutingSession(
- routingSessionInfo = it.routingSessionInfo,
- isMediaOutputDisabled = it.isMediaOutputDisabled,
- isVolumeSeekBarEnabled =
- it.isVolumeSeekBarEnabled && it.routingSessionInfo.volumeMax > 0
- )
- }
- }
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
-
- suspend fun adjustSessionVolume(sessionId: String?, volume: Int) =
- repository.adjustSessionVolume(sessionId, volume)
-}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
index 2d12dae36ff1..caf41f21afb7 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -15,17 +15,12 @@
*/
package com.android.settingslib.volume.data.repository
-import android.media.MediaRoute2Info
-import android.media.MediaRouter2Manager
-import android.media.RoutingSessionInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
-import com.android.settingslib.volume.data.model.RoutingSession
import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -37,15 +32,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class LocalMediaRepositoryImplTest {
@@ -53,7 +43,6 @@ class LocalMediaRepositoryImplTest {
@Mock private lateinit var localMediaManager: LocalMediaManager
@Mock private lateinit var mediaDevice1: MediaDevice
@Mock private lateinit var mediaDevice2: MediaDevice
- @Mock private lateinit var mediaRouter2Manager: MediaRouter2Manager
@Captor
private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
@@ -71,29 +60,11 @@ class LocalMediaRepositoryImplTest {
LocalMediaRepositoryImpl(
eventsReceiver,
localMediaManager,
- mediaRouter2Manager,
testScope.backgroundScope,
- testScope.testScheduler,
)
}
@Test
- fun mediaDevices_areUpdated() {
- testScope.runTest {
- var mediaDevices: Collection<MediaDevice>? = null
- underTest.mediaDevices.onEach { mediaDevices = it }.launchIn(backgroundScope)
- runCurrent()
- verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
- deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
- runCurrent()
-
- assertThat(mediaDevices).hasSize(2)
- assertThat(mediaDevices).contains(mediaDevice1)
- assertThat(mediaDevices).contains(mediaDevice2)
- }
- }
-
- @Test
fun deviceListUpdated_currentConnectedDeviceUpdated() {
testScope.runTest {
var currentConnectedDevice: MediaDevice? = null
@@ -110,78 +81,4 @@ class LocalMediaRepositoryImplTest {
assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
}
}
-
- @Test
- fun kek() {
- testScope.runTest {
- `when`(localMediaManager.remoteRoutingSessions)
- .thenReturn(
- listOf(
- testRoutingSessionInfo1,
- testRoutingSessionInfo2,
- testRoutingSessionInfo3,
- )
- )
- `when`(localMediaManager.shouldEnableVolumeSeekBar(any())).then {
- (it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo1
- }
- `when`(mediaRouter2Manager.getTransferableRoutes(any<RoutingSessionInfo>())).then {
- if ((it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo2) {
- return@then listOf(mock(MediaRoute2Info::class.java))
- }
- emptyList<MediaRoute2Info>()
- }
- var remoteRoutingSessions: Collection<RoutingSession>? = null
- underTest.remoteRoutingSessions
- .onEach { remoteRoutingSessions = it }
- .launchIn(backgroundScope)
-
- runCurrent()
-
- assertThat(remoteRoutingSessions)
- .containsExactlyElementsIn(
- listOf(
- RoutingSession(
- routingSessionInfo = testRoutingSessionInfo1,
- isVolumeSeekBarEnabled = true,
- isMediaOutputDisabled = true,
- ),
- RoutingSession(
- routingSessionInfo = testRoutingSessionInfo2,
- isVolumeSeekBarEnabled = false,
- isMediaOutputDisabled = false,
- ),
- RoutingSession(
- routingSessionInfo = testRoutingSessionInfo3,
- isVolumeSeekBarEnabled = false,
- isMediaOutputDisabled = true,
- )
- )
- )
- }
- }
-
- @Test
- fun adjustSessionVolume_adjusts() {
- testScope.runTest {
- var volume = 0
- `when`(localMediaManager.adjustSessionVolume(anyString(), anyInt())).then {
- volume = it.arguments[1] as Int
- Unit
- }
-
- underTest.adjustSessionVolume("test_session", 10)
-
- assertThat(volume).isEqualTo(10)
- }
- }
-
- private companion object {
- val testRoutingSessionInfo1 =
- RoutingSessionInfo.Builder("id_1", "test.pkg.1").addSelectedRoute("route_1").build()
- val testRoutingSessionInfo2 =
- RoutingSessionInfo.Builder("id_2", "test.pkg.2").addSelectedRoute("route_2").build()
- val testRoutingSessionInfo3 =
- RoutingSessionInfo.Builder("id_3", "test.pkg.3").addSelectedRoute("route_3").build()
- }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index f3d17141334e..964c3f7d13d4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -22,13 +22,10 @@ import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
-import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -37,21 +34,15 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class MediaControllerRepositoryImplTest {
- @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
-
@Mock private lateinit var mediaSessionManager: MediaSessionManager
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
@Mock private lateinit var eventManager: BluetoothEventManager
@@ -103,7 +94,7 @@ class MediaControllerRepositoryImplTest {
}
@Test
- fun playingMediaDevicesAvailable_sessionIsActive() {
+ fun mediaDevicesAvailable_returnsAllActiveOnes() {
testScope.runTest {
`when`(mediaSessionManager.getActiveSessions(any()))
.thenReturn(
@@ -112,53 +103,25 @@ class MediaControllerRepositoryImplTest {
statelessMediaController,
errorMediaController,
remoteMediaController,
- localMediaController
+ localMediaController,
)
)
- var mediaController: MediaController? = null
- underTest.activeLocalMediaController
- .onEach { mediaController = it }
- .launchIn(backgroundScope)
- runCurrent()
- eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
- triggerOnAudioModeChanged()
+ var mediaControllers: Collection<MediaController>? = null
+ underTest.activeSessions.onEach { mediaControllers = it }.launchIn(backgroundScope)
runCurrent()
- assertThat(mediaController).isSameInstanceAs(localMediaController)
- }
- }
-
- @Test
- fun noPlayingMediaDevicesAvailable_sessionIsInactive() {
- testScope.runTest {
- `when`(mediaSessionManager.getActiveSessions(any()))
- .thenReturn(
- listOf(
- stoppedMediaController,
- statelessMediaController,
- errorMediaController,
- )
+ assertThat(mediaControllers)
+ .containsExactly(
+ stoppedMediaController,
+ statelessMediaController,
+ errorMediaController,
+ remoteMediaController,
+ localMediaController,
)
- var mediaController: MediaController? = null
- underTest.activeLocalMediaController
- .onEach { mediaController = it }
- .launchIn(backgroundScope)
- runCurrent()
-
- eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
- triggerOnAudioModeChanged()
- runCurrent()
-
- assertThat(mediaController).isNull()
}
}
- private fun triggerOnAudioModeChanged() {
- verify(eventManager).registerCallback(callbackCaptor.capture())
- callbackCaptor.value.onAudioModeChanged()
- }
-
private companion object {
val statePlaying: PlaybackState =
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 7ec3d243529f..bf4f60d84e4d 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -60,6 +60,7 @@ android_test {
// because this test is not an instrumentation test. (because the target runs in the system process.)
"SettingsProviderLib",
"androidx.test.rules",
+ "frameworks-base-testutils",
"device_config_service_flags_java",
"flag-junit",
"junit",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 3e0d05cd9ecf..1eb04ac1c181 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -98,6 +98,7 @@ public class SettingsHelper {
sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS);
sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
@@ -229,6 +230,10 @@ public class SettingsHelper {
} else if (Settings.System.ACCELEROMETER_ROTATION.equals(name)
&& shouldSkipAutoRotateRestore()) {
return;
+ } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(name)) {
+ // Don't write it to setting. Let the broadcast receiver in
+ // AccessibilityManagerService handle restore/merging logic.
+ return;
}
// Default case: write the restored value to settings
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index 197788e11973..2f8cf4b3d034 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -16,23 +16,31 @@
package com.android.providers.settings;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.concurrent.ExecutionException;
+
/**
* Tests for {@link SettingsHelper#restoreValue(Context, ContentResolver, ContentValues, Uri,
* String, String, int)}. Specifically verifies that we restore critical accessibility settings only
@@ -165,4 +173,33 @@ public class SettingsHelperRestoreTest {
assertEquals(restoreSettingValue, Settings.Secure.getInt(mContentResolver, settingName));
}
+
+ @Test
+ public void restoreAccessibilityQsTargets_broadcastSent()
+ throws ExecutionException, InterruptedException {
+ BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
+ mContext);
+ final String settingName = Settings.Secure.ACCESSIBILITY_QS_TARGETS;
+ final String restoreSettingValue = "com.android.server.accessibility/ColorInversion"
+ + SettingsStringUtil.DELIMITER
+ + "com.android.server.accessibility/ColorCorrectionTile";
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ interceptingContext.nextBroadcastIntent(Intent.ACTION_SETTING_RESTORED);
+
+ mSettingsHelper.restoreValue(
+ interceptingContext,
+ mContentResolver,
+ new ContentValues(2),
+ Settings.Secure.getUriFor(settingName),
+ settingName,
+ restoreSettingValue,
+ Build.VERSION.SDK_INT);
+
+ Intent intentReceived = futureIntent.get();
+ assertThat(intentReceived.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE))
+ .isEqualTo(restoreSettingValue);
+ assertThat(intentReceived.getIntExtra(
+ Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, /* defaultValue= */ 0))
+ .isEqualTo(Build.VERSION.SDK_INT);
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d9c371a79b2d..e346e72516cf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -277,6 +277,9 @@
<!-- to change spatial audio -->
<uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+ <!-- to adjust volume in volume panel -->
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
<!-- to access ResolverRankerServices -->
<uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
index abe1e3de8eea..1c763e8c6108 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt
@@ -181,6 +181,11 @@ private constructor(
turbulenceNoiseShader.setColor(newColor)
}
+ /** Updates the noise color that's screen blended on top. */
+ fun updateScreenColor(newColor: Int) {
+ turbulenceNoiseShader.setScreenColor(newColor)
+ }
+
/**
* Retrieves the noise offset x, y, z values. This is useful for replaying the animation
* smoothly from the last animation, by passing in the last values to the next animation.
@@ -322,7 +327,10 @@ private constructor(
private fun draw() {
paintCallback?.onDraw(paint!!)
renderEffectCallback?.onDraw(
- RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src")
+ RenderEffect.createRuntimeShaderEffect(
+ turbulenceNoiseShader,
+ TurbulenceNoiseShader.BACKGROUND_UNIFORM
+ )
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 59354c843447..ba8f1ace0214 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -52,7 +52,7 @@ data class TurbulenceNoiseAnimationConfig(
/** Color of the effect. */
val color: Int = DEFAULT_COLOR,
/** Background color of the effect. */
- val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+ val screenColor: Int = DEFAULT_SCREEN_COLOR,
val width: Float = 0f,
val height: Float = 0f,
val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS,
@@ -72,7 +72,7 @@ data class TurbulenceNoiseAnimationConfig(
*/
val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS,
/** Whether to flip the luma mask. */
- val shouldInverseNoiseLuminosity: Boolean = false
+ val shouldInverseNoiseLuminosity: Boolean = false,
) {
companion object {
const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -83,7 +83,7 @@ data class TurbulenceNoiseAnimationConfig(
const val DEFAULT_COLOR = Color.WHITE
const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
- const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+ const val DEFAULT_SCREEN_COLOR = Color.BLACK
private val random = Random()
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 8dd90a8ffe9f..025c8b9dce04 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -16,6 +16,7 @@
package com.android.systemui.surfaceeffects.turbulencenoise
import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaders.SolidColorShader
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import java.lang.Float.max
@@ -28,9 +29,11 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
RuntimeShader(getShader(baseType)) {
// language=AGSL
companion object {
+ /** Uniform name for the background buffer (e.g. image, solid color, etc.). */
+ const val BACKGROUND_UNIFORM = "in_src"
private const val UNIFORMS =
"""
- uniform shader in_src; // Needed to support RenderEffect.
+ uniform shader ${BACKGROUND_UNIFORM};
uniform float in_gridNum;
uniform vec3 in_noiseMove;
uniform vec2 in_size;
@@ -41,7 +44,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
uniform half in_lumaMatteBlendFactor;
uniform half in_lumaMatteOverallBrightness;
layout(color) uniform vec4 in_color;
- layout(color) uniform vec4 in_backgroundColor;
+ layout(color) uniform vec4 in_screenColor;
"""
private const val SIMPLEX_SHADER =
@@ -50,22 +53,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
vec2 uv = p / in_size.xy;
uv.x *= in_aspectRatio;
+ // Compute turbulence effect with the uv distorted with simplex noise.
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- // Bring it to [0, 1] range.
- float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
- luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
- * in_opacity;
- vec3 mask = maskLuminosity(in_color.rgb, luma);
- vec3 color = in_backgroundColor.rgb + mask * 0.6;
+ vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);
+
+ // Blend the result with the background color.
+ color = in_src.eval(p).rgb + color * 0.6;
// Add dither with triangle distribution to avoid color banding. Dither in the
// shader here as we are in gamma space.
float dither = triangleNoise(p * in_pixelDensity) / 255.;
+ color += dither.rrr;
- // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
- // multiply rgb with a to get the correct result.
- color = (color + dither.rrr) * in_opacity;
- return vec4(color, in_opacity);
+ // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A].
+ return vec4(color * in_opacity, in_opacity);
}
"""
@@ -76,32 +77,105 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- // Bring it to [0, 1] range.
- float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
- luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
- * in_opacity;
- vec3 mask = maskLuminosity(in_color.rgb, luma);
- vec3 color = in_backgroundColor.rgb + mask * 0.6;
+ vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma);
+
+ // Blend the result with the background color.
+ color = in_src.eval(p).rgb + color * 0.6;
// Skip dithering.
return vec4(color * in_opacity, in_opacity);
}
"""
+
+ /**
+ * This effect has two layers: color turbulence effect with sparkles on top.
+ * 1. Gets the luma matte using Simplex noise.
+ * 2. Generate a colored turbulence layer with the luma matte.
+ * 3. Generate a colored sparkle layer with the same luma matter.
+ * 4. Apply a screen color to the background image.
+ * 5. Composite the previous result with the color turbulence.
+ * 6. Composite the latest result with the sparkles.
+ */
+ private const val SIMPLEX_SPARKLE_SHADER =
+ """
+ vec4 main(vec2 p) {
+ vec2 uv = p / in_size.xy;
+ uv.x *= in_aspectRatio;
+
+ vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+ // Luma is used for both color and sparkle masks.
+ float luma = simplex3d(noiseP) * in_inverseLuma;
+
+ // Get color layer (color mask with in_color applied)
+ vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);
+ float dither = triangleNoise(p * in_pixelDensity) / 255.;
+ colorLayer += dither.rrr;
+
+ // Get sparkle layer (sparkle mask with particles & in_color applied)
+ vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p);
+
+ // Composite with the background.
+ half4 bgColor = in_src.eval(p);
+ half sparkleOpacity = smoothstep(0, 0.75, in_opacity);
+
+ half3 effect = screen(bgColor.rgb, in_screenColor.rgb);
+ effect = screen(effect, colorLayer * 0.22);
+ effect += sparkleLayer * sparkleOpacity;
+
+ return mix(bgColor, vec4(effect, 1.), in_opacity);
+ }
+ """
+
+ private const val COMMON_FUNCTIONS =
+ /**
+ * Below two functions generate turbulence layers (color or sparkles applied) with the
+ * given luma matte. They both return a mask with in_color applied.
+ */
+ """
+ vec3 getColorTurbulenceMask(float luma) {
+ // Bring it to [0, 1] range.
+ luma = luma * 0.5 + 0.5;
+
+ half colorLuma =
+ saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
+ vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma);
+
+ return colorLayer;
+ }
+
+ vec3 getSparkleTurbulenceMask(float luma, vec2 p) {
+ half lumaIntensity = 1.75;
+ half lumaBrightness = -1.3;
+ half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.);
+
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z);
+ vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma);
+
+ return sparkleLayer;
+ }
+ """
private const val SIMPLEX_NOISE_SHADER =
- ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER
private const val FRACTAL_NOISE_SHADER =
- ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
- // TODO (b/282007590): Add NOISE_WITH_SPARKLE
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER
+ private const val SPARKLE_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER
enum class Type {
+ /** Effect with a simple color noise turbulence. */
SIMPLEX_NOISE,
+ /** Effect with a simple color noise turbulence, with fractal. */
SIMPLEX_NOISE_FRACTAL,
+ /** Effect with color & sparkle turbulence with screen color layer. */
+ SIMPLEX_NOISE_SPARKLE
}
fun getShader(type: Type): String {
return when (type) {
Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER
Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER
+ Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER
}
}
}
@@ -111,7 +185,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
setGridCount(config.gridCount)
setPixelDensity(config.pixelDensity)
setColor(config.color)
- setBackgroundColor(config.backgroundColor)
+ setScreenColor(config.screenColor)
setSize(config.width, config.height)
setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity)
@@ -137,9 +211,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
setColorUniform("in_color", color)
}
- /** Sets the background color of the effect. Alpha is ignored. */
+ /**
+ * Sets the color that is used for blending on top of the background color/image. Only relevant
+ * to [Type.SIMPLEX_NOISE_SPARKLE].
+ */
+ fun setScreenColor(color: Int) {
+ setColorUniform("in_screenColor", color)
+ }
+
+ /**
+ * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect],
+ * no need to call this function since the background image of the View will be used.
+ */
fun setBackgroundColor(color: Int) {
- setColorUniform("in_backgroundColor", color)
+ setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color))
}
/**
@@ -163,7 +248,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
*
* @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
* this a lower number removes variations. I.e. the turbulence noise will look more blended.
- * Expected input range is [0, 1]. more dimmed.
+ * Expected input range is [0, 1].
* @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
* Expected input range is [0, 1].
*
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index d78097815b5e..8ad2bb78f5d6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -72,6 +72,7 @@ import com.android.systemui.notifications.ui.composable.Notifications.Transition
import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.shared.model.StackRounding
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -156,6 +157,8 @@ fun SceneScope.NotificationScrollingStack(
val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
+ val stackRounding = viewModel.stackRounding.collectAsState()
+
// the offset for the notifications scrim. Its upper bound is 0, and its lower bound is
// calculated in minScrimOffset. The scrim is the same height as the screen minus the
// height of the Shade Header, and at rest (scrimOffset = 0) its top bound is at maxScrimStartY.
@@ -226,12 +229,7 @@ fun SceneScope.NotificationScrollingStack(
{ expansionFraction },
layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade)
)
- .let {
- RoundedCornerShape(
- topStart = it,
- topEnd = it,
- )
- }
+ .let { stackRounding.value.toRoundedCornerShape(it) }
clip = true
}
) {
@@ -367,7 +365,7 @@ private fun calculateCornerRadius(
lerp(
start = screenCornerRadius.value,
stop = SCRIM_CORNER_RADIUS,
- fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
+ fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceIn(0f, 1f),
)
.dp
} else {
@@ -394,5 +392,16 @@ private fun Modifier.debugBackground(
this
}
+fun StackRounding.toRoundedCornerShape(radius: Dp): RoundedCornerShape {
+ val topRadius = if (roundTop) radius else 0.dp
+ val bottomRadius = if (roundBottom) radius else 0.dp
+ return RoundedCornerShape(
+ topStart = topRadius,
+ topEnd = topRadius,
+ bottomStart = bottomRadius,
+ bottomEnd = bottomRadius,
+ )
+}
+
private const val TAG = "FlexiNotifs"
private val DEBUG_COLOR = Color(1f, 0f, 0f, 0.2f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index bc48dd1d431f..244861c277c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -36,7 +37,8 @@ import com.android.compose.modifiers.thenIf
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
-import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQQS
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.UnsquishingQS
import com.android.systemui.scene.shared.model.Scenes
object QuickSettings {
@@ -49,6 +51,8 @@ object QuickSettings {
object Elements {
val Content =
ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
+ val QuickQuickSettings = ElementKey("QuickQuickSettings")
+ val SplitShadeQuickSettings = ElementKey("SplitShadeQuickSettings")
val FooterActions = ElementKey("QuickSettingsFooterActions")
}
@@ -78,12 +82,16 @@ private fun SceneScope.stateForQuickSettingsContent(
is TransitionState.Transition ->
with(transitionState) {
when {
- isSplitShade -> QSSceneAdapter.State.QS
- fromScene == Scenes.Shade && toScene == Scenes.QuickSettings ->
+ isSplitShade -> UnsquishingQS(squishiness)
+ fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
Expanding(progress)
- fromScene == Scenes.QuickSettings && toScene == Scenes.Shade ->
+ }
+ fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
Collapsing(progress)
- fromScene == Scenes.Shade || toScene == Scenes.Shade -> Unsquishing(squishiness)
+ }
+ fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
+ UnsquishingQQS(squishiness)
+ }
fromScene == Scenes.QuickSettings || toScene == Scenes.QuickSettings -> {
QSSceneAdapter.State.QS
}
@@ -119,6 +127,18 @@ fun SceneScope.QuickSettings(
squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
) {
val contentState = stateForQuickSettingsContent(isSplitShade, squishiness)
+ val transitionState = layoutState.transitionState
+ val isClosing =
+ transitionState is TransitionState.Transition &&
+ transitionState.progress >= 0.9f && // almost done closing
+ !(layoutState.isTransitioning(to = Scenes.Shade) ||
+ layoutState.isTransitioning(to = Scenes.QuickSettings))
+
+ if (isClosing) {
+ DisposableEffect(Unit) {
+ onDispose { qsSceneAdapter.setState(QSSceneAdapter.State.CLOSED) }
+ }
+ }
MovableElement(
key = QuickSettings.Elements.Content,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 5c6e1c89ad65..9b59708fe81d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -13,11 +13,18 @@ fun TransitionBuilder.goneToShadeTransition(
) {
spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.Clock) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.PrivacyChip) }
- translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ fractionRange(start = .58f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ }
+ translate(
+ QuickSettings.Elements.QuickQuickSettings,
+ y = -ShadeHeader.Dimensions.CollapsedHeight * .66f
+ )
translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 15e7b511915e..677fb1d13fa7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -222,15 +222,17 @@ private fun SceneScope.SingleShade(
horizontal = Shade.Dimensions.HorizontalPadding
)
)
- QuickSettings(
- viewModel.qsSceneAdapter,
- {
- (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
- .roundToInt()
- },
- isSplitShade = false,
- squishiness = tileSquishiness,
- )
+ Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) {
+ QuickSettings(
+ viewModel.qsSceneAdapter,
+ {
+ (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
+ .roundToInt()
+ },
+ isSplitShade = false,
+ squishiness = tileSquishiness,
+ )
+ }
MediaIfVisible(
viewModel = viewModel,
@@ -280,6 +282,8 @@ private fun SceneScope.SplitShade(
val lifecycleOwner = LocalLifecycleOwner.current
val footerActionsViewModel =
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
+ val tileSquishiness by
+ animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
@@ -325,12 +329,17 @@ private fun SceneScope.SplitShade(
.padding(bottom = navBarBottomHeight)
}
) {
- QuickSettings(
- qsSceneAdapter = viewModel.qsSceneAdapter,
- heightProvider = { viewModel.qsSceneAdapter.qsHeight },
- isSplitShade = true,
- modifier = Modifier.fillMaxWidth(),
- )
+ Box(
+ modifier = Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ squishiness = tileSquishiness,
+ )
+ }
MediaIfVisible(
viewModel = viewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 3c0ab240cbba..27c4ec125b59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -27,9 +27,17 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -41,8 +49,6 @@ import com.google.common.truth.Truth.assertThat
import java.util.Locale
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -57,8 +63,9 @@ import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
class QSSceneAdapterImplTest : SysuiTestCase() {
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val kosmos = Kosmos().apply { testCase = this@QSSceneAdapterImplTest }
+ private val testDispatcher = kosmos.testDispatcher
+ private val testScope = kosmos.testScope
private val qsImplProvider =
object : Provider<QSImpl> {
@@ -107,10 +114,15 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
}
}
+ private val shadeInteractor = kosmos.shadeInteractor
+ private val dumpManager = mock<DumpManager>()
+
private val underTest =
QSSceneAdapterImpl(
qsSceneComponentFactory,
qsImplProvider,
+ shadeInteractor,
+ dumpManager,
testDispatcher,
testScope.backgroundScope,
configurationInteractor,
@@ -158,12 +170,6 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
)
verify(this).setListening(false)
verify(this).setExpanded(false)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -187,13 +193,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
/* squishinessFraction= */ 1f,
)
verify(this).setListening(true)
- verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
+ verify(this).setExpanded(false)
}
}
@@ -218,12 +218,6 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
)
verify(this).setListening(true)
verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -249,12 +243,6 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
)
verify(this).setListening(true)
verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ 1f,
- )
}
}
@@ -268,7 +256,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
runCurrent()
clearInvocations(qsImpl!!)
- underTest.setState(QSSceneAdapter.State.Unsquishing(squishiness))
+ underTest.setState(QSSceneAdapter.State.UnsquishingQQS(squishiness))
with(qsImpl!!) {
verify(this).setQsVisible(true)
verify(this)
@@ -279,13 +267,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
/* squishinessFraction= */ squishiness,
)
verify(this).setListening(true)
- verify(this).setExpanded(true)
- verify(this)
- .setTransitionToFullShadeProgress(
- /* isTransitioningToFullShade= */ false,
- /* qsTransitionFraction= */ 1f,
- /* qsSquishinessFraction = */ squishiness,
- )
+ verify(this).setExpanded(false)
}
}
@@ -497,4 +479,21 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
verify(qsImpl!!).applyBottomNavBarToCustomizerPadding(navBarHeight)
}
+
+ @Test
+ fun dispatchSplitShade() =
+ testScope.runTest {
+ val shadeRepository = kosmos.fakeShadeRepository
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ verify(qsImpl!!).setInSplitShade(false)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ runCurrent()
+ verify(qsImpl!!).setInSplitShade(true)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index e281383e6250..ebd65fdcd538 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -49,9 +49,16 @@ class QSSceneAdapterTest : SysuiTestCase() {
}
@Test
- fun unsquishing_expansionSameAsQQS() {
+ fun unsquishingQQS_expansionSameAsQQS() {
val squishiness = 0.6f
- assertThat(QSSceneAdapter.State.Unsquishing(squishiness).expansion)
+ assertThat(QSSceneAdapter.State.UnsquishingQQS(squishiness).expansion)
.isEqualTo(QSSceneAdapter.State.QQS.expansion)
}
+
+ @Test
+ fun unsquishingQS_expansionSameAsQS() {
+ val squishiness = 0.6f
+ assertThat(QSSceneAdapter.State.UnsquishingQS(squishiness).expansion)
+ .isEqualTo(QSSceneAdapter.State.QS.expansion)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 2689fc111142..94539a39869e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -31,6 +30,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
@@ -64,7 +64,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
@Test
fun updateBounds() =
testScope.runTest {
- val bounds by collectLastValue(appearanceViewModel.stackBounds)
+ val clipping by collectLastValue(appearanceViewModel.stackClipping)
val top = 200f
val left = 0f
@@ -76,15 +76,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
right = right,
bottom = bottom
)
- assertThat(bounds)
- .isEqualTo(
- NotificationContainerBounds(
- left = left,
- top = top,
- right = right,
- bottom = bottom
- )
- )
+ assertThat(clipping?.bounds)
+ .isEqualTo(StackBounds(left = left, top = top, right = right, bottom = bottom))
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index ffe6e6df6b48..6dd425c2afbc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -19,10 +19,10 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -43,19 +43,17 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
val stackBounds by collectLastValue(underTest.stackBounds)
val bounds1 =
- NotificationContainerBounds(
+ StackBounds(
top = 100f,
bottom = 200f,
- isAnimated = true,
)
underTest.setStackBounds(bounds1)
assertThat(stackBounds).isEqualTo(bounds1)
val bounds2 =
- NotificationContainerBounds(
+ StackBounds(
top = 200f,
bottom = 300f,
- isAnimated = false,
)
underTest.setStackBounds(bounds2)
assertThat(stackBounds).isEqualTo(bounds2)
@@ -65,7 +63,7 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
fun setStackBounds_withImproperBounds_throwsException() =
testScope.runTest {
underTest.setStackBounds(
- NotificationContainerBounds(
+ StackBounds(
top = 100f,
bottom = 99f,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 693de55211f8..2ccc8b44eff8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -22,6 +22,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -36,9 +37,9 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
fun onBoundsChanged_setsNotificationContainerBounds() {
underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value)
- .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ .isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value)
- .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ .isEqualTo(StackBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
}
@Test
fun onContentTopChanged_setsContentTop() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
new file mode 100644
index 000000000000..b5c580978737
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import android.os.Handler
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.localMediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.remoteMediaController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaDeviceSessionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MediaDeviceSessionInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ mediaControllerRepository.setActiveSessions(
+ listOf(localMediaController, remoteMediaController)
+ )
+
+ underTest =
+ MediaDeviceSessionInteractor(
+ testScope.testScheduler,
+ Handler(TestableLooper.get(kosmos.testCase).looper),
+ mediaControllerRepository,
+ )
+ }
+ }
+
+ @Test
+ fun playbackInfo_returnsPlaybackInfo() {
+ with(kosmos) {
+ testScope.runTest {
+ val session by collectLastValue(mediaOutputInteractor.defaultActiveMediaSession)
+ runCurrent()
+ val info by collectLastValue(underTest.playbackInfo(session!!))
+ runCurrent()
+
+ assertThat(info).isEqualTo(localMediaController.playbackInfo)
+ }
+ }
+ }
+
+ @Test
+ fun playbackState_returnsPlaybackState() {
+ with(kosmos) {
+ testScope.runTest {
+ val session by collectLastValue(mediaOutputInteractor.defaultActiveMediaSession)
+ runCurrent()
+ val state by collectLastValue(underTest.playbackState(session!!))
+ runCurrent()
+
+ assertThat(state).isEqualTo(localMediaController.playbackState)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index dcf635e622f4..6f7f20b47199 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -29,9 +29,10 @@ import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.volumePanelViewModel
@@ -63,6 +64,7 @@ class MediaOutputViewModelTest : SysuiTestCase() {
testScope.backgroundScope,
volumePanelViewModel,
mediaOutputActionsInteractor,
+ mediaDeviceSessionInteractor,
mediaOutputInteractor,
)
@@ -74,11 +76,11 @@ class MediaOutputViewModelTest : SysuiTestCase() {
)
}
- whenever(mediaController.packageName).thenReturn("test.pkg")
- whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(mediaController.playbackState).then { playbackStateBuilder.build() }
+ whenever(localMediaController.packageName).thenReturn("test.pkg")
+ whenever(localMediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(localMediaController.playbackState).then { playbackStateBuilder.build() }
- mediaControllerRepository.setActiveLocalMediaController(mediaController)
+ mediaControllerRepository.setActiveSessions(listOf(localMediaController))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 1ed7f5d04622..2f69942aa459 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -32,8 +32,8 @@ import com.android.systemui.media.spatializerRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.panel.component.spatial.spatialAudioComponentInteractor
import com.google.common.truth.Truth.assertThat
@@ -66,11 +66,11 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
}
)
- whenever(mediaController.packageName).thenReturn("test.pkg")
- whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+ whenever(localMediaController.packageName).thenReturn("test.pkg")
+ whenever(localMediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(localMediaController.playbackState).thenReturn(PlaybackState.Builder().build())
- mediaControllerRepository.setActiveLocalMediaController(mediaController)
+ mediaControllerRepository.setActiveSessions(listOf(localMediaController))
underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 281b03d69536..e36ae60ebe7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -34,8 +34,8 @@ import com.android.systemui.media.spatializerRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
@@ -70,11 +70,11 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() {
}
)
- whenever(mediaController.packageName).thenReturn("test.pkg")
- whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+ whenever(localMediaController.packageName).thenReturn("test.pkg")
+ whenever(localMediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(localMediaController.playbackState).thenReturn(PlaybackState.Builder().build())
- mediaControllerRepository.setActiveLocalMediaController(mediaController)
+ mediaControllerRepository.setActiveSessions(listOf(localMediaController))
underTest =
SpatialAudioComponentInteractor(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index a8999ff31f8a..6c8949e51094 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
@@ -16,6 +16,7 @@ package com.android.systemui.plugins.qs;
import android.content.Context;
import android.view.View;
+import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import com.android.systemui.plugins.annotations.DependsOn;
@@ -74,4 +75,9 @@ public abstract class QSTileView extends LinearLayout {
/** Sets the index of this tile in its layout */
public abstract void setPosition(int position);
+
+ /** Get the duration of a visuo-haptic long-press effect */
+ public int getLongPressEffectDuration() {
+ return ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+ }
}
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index efdb0a360031..704cf0b61b1b 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -29,9 +29,13 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
+ android:id="@+id/magnifier_size_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
android:text="@string/accessibility_magnifier_size"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:focusable="true"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 774bbe504b03..bf5eeb9e8294 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1437,8 +1437,11 @@
<!-- Indication on the keyguard that appears when a trust agents unlocks the device. [CHAR LIMIT=40] -->
<string name="keyguard_indication_trust_unlocked">Kept unlocked by TrustAgent</string>
- <!-- Message asking the user to authenticate with primary authentication methods (PIN/pattern/password) or biometrics after the device is locked by adaptive auth. [CHAR LIMIT=60] -->
- <string name="kg_prompt_after_adaptive_auth_lock">Theft protection\nDevice locked, too many unlock attempts</string>
+ <!-- Message asking the user to authenticate with primary authentication methods (PIN/pattern/password) or biometrics after the device is locked by adaptive auth. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_after_adaptive_auth_lock">Device was locked, too many authentication attempts</string>
+
+ <!-- Indication on the keyguard that appears after the device is locked by adaptive auth. [CHAR LIMIT=60] -->
+ <string name="keyguard_indication_after_adaptive_auth_lock">Device locked\nFailed authentication</string>
<!-- Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. [CHAR LIMIT=20] -->
<string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string>
@@ -1991,8 +1994,6 @@
<string name="group_system_cycle_back">Cycle backward through recent apps</string>
<!-- User visible title for the keyboard shortcut that accesses list of all apps and search. [CHAR LIMIT=70] -->
<string name="group_system_access_all_apps_search">Open apps list</string>
- <!-- User visible title for the keyboard shortcut that hides and (re)showes taskbar. [CHAR LIMIT=70] -->
- <string name="group_system_hide_reshow_taskbar">Show taskbar</string>
<!-- User visible title for the keyboard shortcut that accesses [system] settings. [CHAR LIMIT=70] -->
<string name="group_system_access_system_settings">Open settings</string>
<!-- User visible title for the keyboard shortcut that accesses Assistant app. [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 59516be65a5e..0483a0734a83 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -962,7 +962,7 @@
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowCloseOnTouchOutside">true</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:windowAnimationStyle">@null</item>
</style>
<style name="Widget.SliceView.VolumePanel">
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index a98990af00c7..ca24ccb3e6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -98,6 +98,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
private ImageButton mMediumButton;
private ImageButton mLargeButton;
private Button mDoneButton;
+ private TextView mSizeTitle;
private Button mEditButton;
private ImageButton mFullScreenButton;
private int mLastSelectedButtonIndex = MagnificationSize.NONE;
@@ -521,6 +522,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
+ mSizeTitle = mSettingView.findViewById(R.id.magnifier_size_title);
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button);
mAllowDiagonalScrollingTitle =
@@ -548,6 +550,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
mDoneButton.setOnClickListener(mButtonClickListener);
mFullScreenButton.setOnClickListener(mButtonClickListener);
mEditButton.setOnClickListener(mButtonClickListener);
+ mSizeTitle.setSelected(true);
mAllowDiagonalScrollingTitle.setSelected(true);
mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index ac99fc69b2b5..85f63e9f1974 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -126,6 +126,7 @@ constructor(
field?.let { oldView ->
val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
lottie.pauseAnimation()
+ lottie.removeAllLottieOnCompositionLoadedListener()
windowManager.removeView(oldView)
orientationListener.disable()
}
@@ -288,7 +289,7 @@ constructor(
}
private fun onOrientationChanged(@BiometricRequestConstants.RequestReason reason: Int) {
- if (overlayView != null) {
+ if (overlayView?.isAttachedToWindow == true) {
createOverlayForDisplay(reason)
}
}
@@ -322,7 +323,7 @@ constructor(
)
lottie.addLottieOnCompositionLoadedListener {
// Check that view is not stale, and that overlayView has not been hidden/removed
- if (overlayView != null && overlayView == view) {
+ if (overlayView?.isAttachedToWindow == true && overlayView == view) {
updateOverlayParams(display, it.bounds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index ed1557cccd01..c4967ec0df21 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -23,6 +23,7 @@ import com.android.systemui.biometrics.shared.model.AuthenticationReason.Setting
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
/** Encapsulates business logic for interacting with biometric authentication state. */
@@ -52,7 +53,7 @@ constructor(
} else {
AuthenticationReason.NotRunning
}
- }
+ }.distinctUntilChanged()
override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
biometricStatusRepository.fingerprintAcquiredStatus
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
index 3063ebd60b0c..fdd98bec0a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt
@@ -18,12 +18,8 @@ package com.android.systemui.common.shared.model
/** Models the bounds of the notification container. */
data class NotificationContainerBounds(
- /** The position of the left of the container in its window coordinate system, in pixels. */
- val left: Float = 0f,
/** The position of the top of the container in its window coordinate system, in pixels. */
val top: Float = 0f,
- /** The position of the right of the container in its window coordinate system, in pixels. */
- val right: Float = 0f,
/** The position of the bottom of the container in its window coordinate system, in pixels. */
val bottom: Float = 0f,
/** Whether any modifications to top/bottom should be smoothly animated. */
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index ec72a1422973..f1620d96b159 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -214,6 +214,24 @@ class QSLongPressEffect(
_actionType.value = null
}
+ /**
+ * Reset the effect with a new effect duration.
+ *
+ * The effect will go back to an [IDLE] state where it can begin its logic with a new duration.
+ *
+ * @param[duration] New duration for the long-press effect
+ */
+ fun resetWithDuration(duration: Int) {
+ // The effect can't reset if it is running
+ if (effectAnimator.isRunning) return
+
+ effectAnimator.duration = duration.toLong()
+ _effectProgress.value = 0f
+ _actionType.value = null
+ waitJob?.cancel()
+ state = State.IDLE
+ }
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 9a6088de110e..7f752b468c13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -231,5 +231,6 @@ constructor(
private val DEFAULT_DURATION = 500.milliseconds
val TO_GLANCEABLE_HUB_DURATION = 1.seconds
val TO_LOCKSCREEN_DURATION = 1167.milliseconds
+ val TO_GONE_DURATION = DEFAULT_DURATION
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index b8ba09801ee8..5de1a61d61b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -17,10 +17,10 @@ package com.android.systemui.keyguard.ui
import android.view.animation.Interpolator
import com.android.app.animation.Interpolators.LINEAR
-import com.android.app.tracing.coroutines.launch
import com.android.keyguard.logging.KeyguardTransitionAnimationLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -35,6 +35,7 @@ import kotlin.math.max
import kotlin.math.min
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
@@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -52,13 +54,14 @@ class KeyguardTransitionAnimationFlow
@Inject
constructor(
@Application private val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val transitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardTransitionAnimationLogger,
) {
private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
init {
- scope.launch("KeyguardTransitionAnimationFlow") {
+ scope.launch(mainDispatcher) {
transitionInteractor.transitions.collect {
// FROM->TO
transitionMap[Edge(it.from, it.to)]?.emit(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 3630b4038357..397cbe5b3e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -22,11 +22,16 @@ import android.graphics.drawable.Animatable2
import android.util.Size
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.WindowInsets
import android.widget.ImageView
import androidx.core.animation.CycleInterpolator
import androidx.core.animation.ObjectAnimator
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
+import androidx.core.view.marginLeft
+import androidx.core.view.marginRight
+import androidx.core.view.marginTop
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -114,6 +119,38 @@ object KeyguardBottomAreaViewBinder {
val settingsMenu: LaunchableLinearLayout =
view.requireViewById(R.id.keyguard_settings_button)
+ startButton.setOnApplyWindowInsetsListener { inView, windowInsets ->
+ val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0
+ val marginBottom =
+ inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt()
+ inView.layoutParams =
+ (inView.layoutParams as MarginLayoutParams).apply {
+ setMargins(
+ inView.marginLeft,
+ inView.marginTop,
+ inView.marginRight,
+ marginBottom + bottomInset
+ )
+ }
+ WindowInsets.CONSUMED
+ }
+
+ endButton.setOnApplyWindowInsetsListener { inView, windowInsets ->
+ val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0
+ val marginBottom =
+ inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt()
+ inView.layoutParams =
+ (inView.layoutParams as MarginLayoutParams).apply {
+ setMargins(
+ inView.marginLeft,
+ inView.marginTop,
+ inView.marginRight,
+ marginBottom + bottomInset
+ )
+ }
+ WindowInsets.CONSUMED
+ }
+
view.clipChildren = false
view.clipToPadding = false
view.setOnTouchListener { _, event ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index fc95ec927a4c..d0246a8cd872 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -44,6 +44,7 @@ import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -142,12 +143,14 @@ object KeyguardRootViewBinder {
}
}
- if (keyguardBottomAreaRefactor()) {
+ if (keyguardBottomAreaRefactor() || DeviceEntryUdfpsRefactor.isEnabled) {
launch {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
- childViews[statusViewId]?.alpha = alpha
- childViews[burnInLayerId]?.alpha = alpha
+ if (keyguardBottomAreaRefactor()) {
+ childViews[statusViewId]?.alpha = alpha
+ childViews[burnInLayerId]?.alpha = alpha
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
new file mode 100644
index 000000000000..ec7b931161f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+@SysUISingleton
+class DreamingToGoneTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) {
+
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromDreamingTransitionInteractor.TO_GONE_DURATION,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GONE,
+ )
+
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 55a402597d5b..301f00ee38db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -85,10 +85,12 @@ constructor(
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+ private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel,
private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -136,14 +138,20 @@ constructor(
}
.distinctUntilChanged()
+ private val lockscreenToGoneTransitionRunning: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .isInTransitionWhere { from, to -> from == LOCKSCREEN && to == GONE }
+ .onStart { emit(false) }
+
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
+ lockscreenToGoneTransitionRunning,
isOnLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
- ) { isOnLockscreen, qsExpansion, shadeExpansion ->
+ ) { lockscreenToGoneTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion ->
// Fade out quickly as the shade expands
- if (isOnLockscreen) {
+ if (isOnLockscreen && !lockscreenToGoneTransitionRunning) {
val alpha =
1f -
MathUtils.constrainedMap(
@@ -204,10 +212,12 @@ constructor(
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
+ goneToDreamingTransitionViewModel.lockscreenAlpha,
lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState),
lockscreenToDozingTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 5f7991e62cd7..1c11178b5b35 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -24,12 +24,13 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.util.Assert
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.launch
/**
* Runs the given [block] every time the [View] becomes attached (or immediately after calling this
@@ -66,7 +67,8 @@ fun View.repeatWhenAttached(
// dispatcher to use. We don't want it to run on the Dispatchers.Default thread pool as
// default behavior. Instead, we want it to run on the view's UI thread since the user will
// presumably want to call view methods that require being called from said UI thread.
- val lifecycleCoroutineContext = Dispatchers.Main + coroutineContext
+ val lifecycleCoroutineContext =
+ Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
var lifecycleOwner: ViewLifecycleOwner? = null
val onAttachListener =
object : View.OnAttachStateChangeListener {
@@ -97,14 +99,12 @@ fun View.repeatWhenAttached(
)
}
- return object : DisposableHandle {
- override fun dispose() {
- Assert.isMainThread()
+ return DisposableHandle {
+ Assert.isMainThread()
- lifecycleOwner?.onDestroy()
- lifecycleOwner = null
- view.removeOnAttachStateChangeListener(onAttachListener)
- }
+ lifecycleOwner?.onDestroy()
+ lifecycleOwner = null
+ view.removeOnAttachStateChangeListener(onAttachListener)
}
}
@@ -115,7 +115,12 @@ private fun createLifecycleOwnerAndRun(
): ViewLifecycleOwner {
return ViewLifecycleOwner(view).apply {
onCreate()
- lifecycleScope.launch(coroutineContext) { block(view) }
+ lifecycleScope.launch(
+ "ViewLifecycleOwner(${view::class.java.simpleName})",
+ coroutineContext
+ ) {
+ block(view)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 26c63f31fa46..899b9ed103cd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -1306,7 +1306,7 @@ public class MediaControlPanel {
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
// Color will be correctly updated in ColorSchemeTransition.
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
- /* backgroundColor= */ Color.BLACK,
+ /* screenColor= */ Color.BLACK,
width,
height,
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 1d820a14be4e..0a880293ca76 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -21,6 +21,9 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
+import static android.appwidget.flags.Flags.generatedPreviews;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
@@ -56,6 +59,7 @@ import android.app.people.IPeopleManager;
import android.app.people.PeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -80,12 +84,15 @@ import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.widget.RemoteViews;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -96,6 +103,8 @@ import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.people.PeopleTileViewHelper;
import com.android.systemui.people.SharedPreferencesHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -160,13 +169,27 @@ public class PeopleSpaceWidgetManager implements Dumpable {
@GuardedBy("mLock")
public static Map<Integer, PeopleSpaceTile> mTiles = new HashMap<>();
+ @NonNull private final UserTracker mUserTracker;
+ @NonNull private final SparseBooleanArray mUpdatedPreviews = new SparseBooleanArray();
+ @NonNull private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onUserUnlocked() {
+ if (DEBUG) {
+ Log.d(TAG, "onUserUnlocked " + mUserTracker.getUserId());
+ }
+ updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ }
+ };
+
@Inject
public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
CommonNotifCollection notifCollection,
PackageManager packageManager, Optional<Bubbles> bubblesOptional,
UserManager userManager, NotificationManager notificationManager,
BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
- DumpManager dumpManager) {
+ DumpManager dumpManager, @NonNull UserTracker userTracker,
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
mAppWidgetManager = AppWidgetManager.getInstance(context);
@@ -187,6 +210,8 @@ public class PeopleSpaceWidgetManager implements Dumpable {
mBroadcastDispatcher = broadcastDispatcher;
mBgExecutor = bgExecutor;
dumpManager.registerNormalDumpable(TAG, this);
+ mUserTracker = userTracker;
+ keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
}
/** Initializes {@PeopleSpaceWidgetManager}. */
@@ -246,7 +271,7 @@ public class PeopleSpaceWidgetManager implements Dumpable {
CommonNotifCollection notifCollection, PackageManager packageManager,
Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
INotificationManager iNotificationManager, NotificationManager notificationManager,
- @Background Executor executor) {
+ @Background Executor executor, UserTracker userTracker) {
mContext = context;
mAppWidgetManager = appWidgetManager;
mIPeopleManager = iPeopleManager;
@@ -262,6 +287,7 @@ public class PeopleSpaceWidgetManager implements Dumpable {
mManager = this;
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
mBgExecutor = executor;
+ mUserTracker = userTracker;
}
/**
@@ -1407,4 +1433,32 @@ public class PeopleSpaceWidgetManager implements Dumpable {
Trace.traceEnd(Trace.TRACE_TAG_APP);
}
+
+ @VisibleForTesting
+ void updateGeneratedPreviewForUser(UserHandle user) {
+ if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
+ || !mUserManager.isUserUnlocked(user)) {
+ return;
+ }
+
+ // The widget provider may be disabled on SystemUI implementers, e.g. TvSystemUI.
+ ComponentName provider = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
+ List<AppWidgetProviderInfo> infos = mAppWidgetManager.getInstalledProvidersForPackage(
+ mContext.getPackageName(), user);
+ if (infos.stream().noneMatch(info -> info.provider.equals(provider))) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
+ }
+ boolean success = mAppWidgetManager.setWidgetPreview(
+ provider, WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
+ new RemoteViews(mContext.getPackageName(),
+ R.layout.people_space_placeholder_layout));
+ if (DEBUG && !success) {
+ Log.d(TAG, "Failed to update generated preview for user " + user.getIdentifier());
+ }
+ mUpdatedPreviews.put(user.getIdentifier(), success);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 7a7ee59fa63f..00757b7bd51a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -127,8 +127,9 @@ public class QSPanel extends LinearLayout implements Tunable {
}
- void initialize(QSLogger qsLogger) {
+ void initialize(QSLogger qsLogger, boolean usingMediaPlayer) {
mQsLogger = qsLogger;
+ mUsingMediaPlayer = usingMediaPlayer;
mTileLayout = getOrCreateTileLayout();
if (mUsingMediaPlayer) {
@@ -163,22 +164,25 @@ public class QSPanel extends LinearLayout implements Tunable {
}
protected void setHorizontalContentContainerClipping() {
- mHorizontalContentContainer.setClipChildren(true);
- mHorizontalContentContainer.setClipToPadding(false);
- // Don't clip on the top, that way, secondary pages tiles can animate up
- // Clipping coordinates should be relative to this view, not absolute (parent coordinates)
- mHorizontalContentContainer.addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if ((right - left) != (oldRight - oldLeft)
- || ((bottom - top) != (oldBottom - oldTop))) {
- mClippingRect.right = right - left;
- mClippingRect.bottom = bottom - top;
- mHorizontalContentContainer.setClipBounds(mClippingRect);
- }
- });
- mClippingRect.left = 0;
- mClippingRect.top = -1000;
- mHorizontalContentContainer.setClipBounds(mClippingRect);
+ if (mHorizontalContentContainer != null) {
+ mHorizontalContentContainer.setClipChildren(true);
+ mHorizontalContentContainer.setClipToPadding(false);
+ // Don't clip on the top, that way, secondary pages tiles can animate up
+ // Clipping coordinates should be relative to this view, not absolute
+ // (parent coordinates)
+ mHorizontalContentContainer.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if ((right - left) != (oldRight - oldLeft)
+ || ((bottom - top) != (oldBottom - oldTop))) {
+ mClippingRect.right = right - left;
+ mClippingRect.bottom = bottom - top;
+ mHorizontalContentContainer.setClipBounds(mClippingRect);
+ }
+ });
+ mClippingRect.left = 0;
+ mClippingRect.top = -1000;
+ mHorizontalContentContainer.setClipBounds(mClippingRect);
+ }
}
/**
@@ -412,7 +416,7 @@ public class QSPanel extends LinearLayout implements Tunable {
}
private void updateHorizontalLinearLayoutMargins() {
- if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
+ if (mUsingMediaPlayer && mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
lp.bottomMargin = Math.max(mMediaTotalBottomMargin - getPaddingBottom(), 0);
mHorizontalLinearLayout.setLayoutParams(lp);
@@ -461,6 +465,11 @@ public class QSPanel extends LinearLayout implements Tunable {
/** Call when orientation has changed and MediaHost needs to be adjusted. */
private void reAttachMediaHost(ViewGroup hostView, boolean horizontal) {
if (!mUsingMediaPlayer) {
+ // If the host view was attached, detach it.
+ ViewGroup parent = (ViewGroup) hostView.getParent();
+ if (parent != null) {
+ parent.removeView(hostView);
+ }
return;
}
mMediaHostView = hostView;
@@ -492,8 +501,10 @@ public class QSPanel extends LinearLayout implements Tunable {
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ if (!mExpanded && mTileLayout instanceof PagedTileLayout tilesLayout) {
+ // Use post, so it will wait until the view is attached. If the view is not attached,
+ // it will not populate corresponding views (and will not do it later when attached).
+ tilesLayout.post(() -> tilesLayout.setCurrentItem(0, false));
}
}
@@ -616,7 +627,10 @@ public class QSPanel extends LinearLayout implements Tunable {
if (horizontal != mUsingHorizontalLayout || force) {
Log.d(getDumpableTag(), "setUsingHorizontalLayout: " + horizontal + ", " + force);
mUsingHorizontalLayout = horizontal;
- ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
+ // The tile layout should be reparented if horizontal and we are using media. If not
+ // using media, the parent should always be this.
+ ViewGroup newParent =
+ horizontal && mUsingMediaPlayer ? mHorizontalContentContainer : this;
switchAllContentToParent(newParent, mTileLayout);
reAttachMediaHost(mediaHostView, horizontal);
if (needsDynamicRowsAndColumns()) {
@@ -624,7 +638,9 @@ public class QSPanel extends LinearLayout implements Tunable {
mTileLayout.setMaxColumns(horizontal ? 2 : 4);
}
updateMargins(mediaHostView);
- mHorizontalLinearLayout.setVisibility(horizontal ? View.VISIBLE : View.GONE);
+ if (mHorizontalLinearLayout != null) {
+ mHorizontalLinearLayout.setVisibility(horizontal ? View.VISIBLE : View.GONE);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 5e12b9d4cc34..d8e81875bbbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -167,7 +167,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
@Override
protected void onInit() {
- mView.initialize(mQSLogger);
+ mView.initialize(mQSLogger, mUsingMediaPlayer);
mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
mHost.addCallback(mQSHostCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 63963ded2923..e1c543f8f025 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -37,7 +37,6 @@ import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
-import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
@@ -185,6 +184,8 @@ open class QSTileViewImpl @JvmOverloads constructor(
private var initialLongPressProperties: QSLongPressProperties? = null
private var finalLongPressProperties: QSLongPressProperties? = null
private val colorEvaluator = ArgbEvaluator.getInstance()
+ val hasLongPressEffect: Boolean
+ get() = longPressEffect != null
init {
val typedValue = TypedValue()
@@ -611,10 +612,9 @@ open class QSTileViewImpl @JvmOverloads constructor(
// Long-press effects
if (quickSettingsVisualHapticsLongpress()){
- if (state.handlesLongClick) {
- // initialize the long-press effect and set it as the touch listener
+ if (state.handlesLongClick && maybeCreateAndInitializeLongPressEffect()) {
+ // set the valid long-press effect as the touch listener
showRippleEffect = false
- initializeLongPressEffect()
setOnTouchListener(longPressEffect)
QSLongPressEffectViewBinder.bind(this, longPressEffect)
} else {
@@ -751,7 +751,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
fun updateLongPressEffectProperties(effectProgress: Float) {
- if (!isLongClickable) return
+ if (!isLongClickable || longPressEffect == null) return
setAllColors(
colorEvaluator.evaluate(
effectProgress,
@@ -836,13 +836,25 @@ open class QSTileViewImpl @JvmOverloads constructor(
icon.setTint(icon.mIcon as ImageView, lastIconTint)
}
- private fun initializeLongPressEffect() {
+ private fun maybeCreateAndInitializeLongPressEffect(): Boolean {
+ // Don't setup the effect if the long-press duration is invalid
+ val effectDuration = longPressEffectDuration
+ if (effectDuration <= 0) {
+ longPressEffect = null
+ return false
+ }
+
initializeLongPressProperties()
- longPressEffect =
- QSLongPressEffect(
- vibratorHelper,
- ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
- )
+ if (longPressEffect == null) {
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ effectDuration,
+ )
+ } else {
+ longPressEffect?.resetWithDuration(effectDuration)
+ }
+ return true
}
private fun initializeLongPressProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index c1b20374dbac..671050477042 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -23,16 +23,21 @@ import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import com.android.settingslib.applications.InterestingConfigChanges
+import com.android.systemui.Dumpable
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.QSContainerImpl
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
+import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
import kotlin.coroutines.resume
@@ -107,11 +112,17 @@ interface QSSceneAdapter {
}
/** State for appearing QQS from Lockscreen or Gone */
- data class Unsquishing(override val squishiness: Float) : State {
+ data class UnsquishingQQS(override val squishiness: Float) : State {
override val isVisible = true
override val expansion = 0f
}
+ /** State for appearing QS from Lockscreen or Gone, used in Split shade */
+ data class UnsquishingQS(override val squishiness: Float) : State {
+ override val isVisible = true
+ override val expansion = 1f
+ }
+
companion object {
// These are special cases of the expansion.
val QQS = Expanding(0f)
@@ -129,22 +140,28 @@ class QSSceneAdapterImpl
constructor(
private val qsSceneComponentFactory: QSSceneComponent.Factory,
private val qsImplProvider: Provider<QSImpl>,
+ shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application applicationScope: CoroutineScope,
private val configurationInteractor: ConfigurationInteractor,
private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
-) : QSContainerController, QSSceneAdapter {
+) : QSContainerController, QSSceneAdapter, Dumpable {
@Inject
constructor(
qsSceneComponentFactory: QSSceneComponent.Factory,
qsImplProvider: Provider<QSImpl>,
+ shadeInteractor: ShadeInteractor,
+ dumpManager: DumpManager,
@Main dispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
configurationInteractor: ConfigurationInteractor,
) : this(
qsSceneComponentFactory,
qsImplProvider,
+ shadeInteractor,
+ dumpManager,
dispatcher,
scope,
configurationInteractor,
@@ -182,6 +199,7 @@ constructor(
)
init {
+ dumpManager.registerDumpable(this)
applicationScope.launch {
launch {
state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
@@ -210,6 +228,11 @@ constructor(
it.second.applyBottomNavBarToCustomizerPadding(it.first)
}
}
+ launch {
+ shadeInteractor.shadeMode.collect {
+ qsImpl.value?.setInSplitShade(it == ShadeMode.Split)
+ }
+ }
}
}
@@ -256,9 +279,17 @@ constructor(
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
- setExpanded(state.isVisible)
+ setExpanded(state.isVisible && state.expansion > 0f)
setListening(state.isVisible)
setQsExpansion(state.expansion, 1f, 0f, state.squishiness)
- setTransitionToFullShadeProgress(false, 1f, state.squishiness)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("Last state: ${state.value}")
+ println("Customizing: ${isCustomizing.value}")
+ println("QQS height: $qqsHeight")
+ println("QS height: $qsHeight")
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d0ff33869a77..7c1a2c032bea 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -86,7 +86,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -146,7 +145,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final SceneContainerFlags mSceneContainerFlags;
private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
@@ -209,8 +207,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void onStatusBarTouchEvent(MotionEvent event) {
verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
- // TODO move this logic to message queue
- if (event.getActionMasked() == ACTION_DOWN) {
+ if (mSceneContainerFlags.isEnabled()) {
+ //TODO(b/329863123) implement latency tracking for shade scene
+ Log.i(TAG_OPS, "Scene container enabled. Latency tracking not started.");
+ } else if (event.getActionMasked() == ACTION_DOWN) {
mShadeViewControllerLazy.get().startExpandLatencyTracking();
}
mHandler.post(() -> {
@@ -600,7 +600,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager,
AssistUtils assistUtils,
- FeatureFlags featureFlags,
SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
@@ -613,7 +612,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
mContext = context;
- mFeatureFlags = featureFlags;
mSceneContainerFlags = sceneContainerFlags;
mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3a2a081663cb..9cb920ab0a88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3257,7 +3257,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
}
- @Override
public void performHapticFeedback(int constant) {
mVibratorHelper.performHapticFeedback(mView, constant);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 817f0eae815b..8dbceadbb7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -979,9 +979,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
void updateQsState() {
boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
- if (!FooterViewRefactor.isEnabled()) {
- mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
- }
+ mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
if (!SceneContainerFlag.isEnabled()) {
mNotificationStackScrollLayoutController.setScrollingEnabled(
mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
@@ -1282,18 +1280,20 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
mScrimController.setScrimCornerRadius(radius);
- // Convert global clipping coordinates to local ones,
- // relative to NotificationStackScrollLayout
- int nsslLeft = calculateNsslLeft(left);
- int nsslRight = calculateNsslRight(right);
- int nsslTop = getNotificationsClippingTopBounds(top);
- int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
- int bottomRadius = mSplitShadeEnabled ? radius : 0;
- // TODO (b/265193930): remove dependency on NPVC
- int topRadius = mSplitShadeEnabled
- && mPanelViewControllerLazy.get().isExpandingFromHeadsUp() ? 0 : radius;
- mNotificationStackScrollLayoutController.setRoundedClippingBounds(
- nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
+ if (!SceneContainerFlag.isEnabled()) {
+ // Convert global clipping coordinates to local ones,
+ // relative to NotificationStackScrollLayout
+ int nsslLeft = calculateNsslLeft(left);
+ int nsslRight = calculateNsslRight(right);
+ int nsslTop = getNotificationsClippingTopBounds(top);
+ int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
+ int bottomRadius = mSplitShadeEnabled ? radius : 0;
+ // TODO (b/265193930): remove dependency on NPVC
+ int topRadius = mSplitShadeEnabled
+ && mPanelViewControllerLazy.get().isExpandingFromHeadsUp() ? 0 : radius;
+ mNotificationStackScrollLayoutController.setRoundedClippingBounds(
+ nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 0a57b64b1ecf..813df1127fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -232,6 +232,13 @@ public interface ShadeController extends CoreStartable {
/** Called when a launch animation ends. */
void onLaunchAnimationEnd(boolean launchIsFullScreen);
+ /**
+ * Performs haptic feedback from a view with a haptic feedback constant.
+ *
+ * @param constant One of android.view.HapticFeedbackConstants
+ */
+ void performHapticFeedback(int constant);
+
/** Sets the listener for when the visibility of the shade changes. */
default void setVisibilityListener(ShadeVisibilityListener listener) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 093690ffb881..d703a2763e75 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -63,4 +63,5 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
override fun onStatusBarTouch(event: MotionEvent?) {}
override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {}
override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {}
+ override fun performHapticFeedback(constant: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d99d607879cc..5f5e5cedff84 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -271,6 +271,11 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
}
@Override
+ public void performHapticFeedback(int constant) {
+ getNpvc().performHapticFeedback(constant);
+ }
+
+ @Override
public void instantCollapseShade() {
getNpvc().instantCollapse();
runPostCollapseActions();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 177c3db6b720..c20efea4700e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -33,6 +33,7 @@ import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import dagger.Lazy
@@ -62,6 +63,7 @@ constructor(
private val deviceEntryInteractor: DeviceEntryInteractor,
private val notificationStackScrollLayout: NotificationStackScrollLayout,
@ShadeTouchLog private val touchLog: LogBuffer,
+ private val vibratorHelper: VibratorHelper,
commandQueue: CommandQueue,
statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
notificationShadeWindowController: NotificationShadeWindowController,
@@ -249,4 +251,8 @@ constructor(
// The only call to this doesn't happen with migrateClocksToBlueprint() enabled
throw UnsupportedOperationException()
}
+
+ override fun performHapticFeedback(constant: Int) {
+ vibratorHelper.performHapticFeedback(notificationStackScrollLayout, constant)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d90bb0b98056..9902a32a536d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -44,6 +44,7 @@ interface ShadeViewController {
fun disableHeader(state1: Int, state2: Int, animated: Boolean)
/** If the latency tracker is enabled, begins tracking expand latency. */
+ @Deprecated("No longer supported. Do not add new calls to this.")
fun startExpandLatencyTracking()
/** Sets the alpha value of the shade to a value between 0 and 255. */
@@ -57,13 +58,14 @@ interface ShadeViewController {
fun setAlphaChangeAnimationEndAction(r: Runnable)
/** Sets Qs ScrimEnabled and updates QS state. */
+ @Deprecated("Does nothing when scene container is enabled.")
fun setQsScrimEnabled(qsScrimEnabled: Boolean)
/** Sets the top spacing for the ambient indicator. */
fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean)
/** Updates notification panel-specific flags on [SysUiState]. */
- fun updateSystemUiStateFlags()
+ @Deprecated("Does nothing when scene container is enabled.") fun updateSystemUiStateFlags()
/** Ensures that the touchable region is updated. */
fun updateTouchableRegion()
@@ -105,16 +107,6 @@ interface ShadeViewController {
@Deprecated("No longer supported. Do not add new calls to this.")
fun finishInputFocusTransfer(velocity: Float)
- /**
- * Performs haptic feedback from a view with a haptic feedback constant.
- *
- * The implementation of this method should use the [android.view.View.performHapticFeedback]
- * method with the provided constant.
- *
- * @param[constant] One of [android.view.HapticFeedbackConstants]
- */
- fun performHapticFeedback(constant: Int)
-
/** Returns the ShadeHeadsUpTracker. */
val shadeHeadsUpTracker: ShadeHeadsUpTracker
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 69849e826535..93c3772c6e36 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -84,8 +84,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() :
override fun startInputFocusTransfer() {}
override fun cancelInputFocusTransfer() {}
override fun finishInputFocusTransfer(velocity: Float) {}
- override fun performHapticFeedback(constant: Int) {}
-
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
@Deprecated("Use SceneInteractor.currentScene instead.")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index a12b9709a063..da8c1bebce92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -560,11 +560,6 @@ public final class KeyboardShortcutListSearch {
Pair.create(
KeyEvent.KEYCODE_TAB,
KeyEvent.META_SHIFT_ON | KeyEvent.META_ALT_ON))),
- /* Hide and (re)show taskbar: Meta + T */
- new ShortcutKeyGroupMultiMappingInfo(
- context.getString(R.string.group_system_hide_reshow_taskbar),
- Arrays.asList(
- Pair.create(KeyEvent.KEYCODE_T, KeyEvent.META_META_ON))),
/* Access notification shade: Meta + N */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_access_notification_shade),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c9046217e68a..815236e0820c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -757,8 +757,8 @@ public class KeyguardIndicationController {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_ADAPTIVE_AUTH,
new KeyguardIndication.Builder()
- .setMessage(mContext
- .getString(R.string.kg_prompt_after_adaptive_auth_lock))
+ .setMessage(mContext.getString(
+ R.string.keyguard_indication_after_adaptive_auth_lock))
.setTextColor(mInitialTextColorState)
.build(),
true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 947976299f8e..f2c593d7ffdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -812,6 +812,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
} else {
mDebugTextUsedYPositions.clear();
}
+
+ mDebugPaint.setColor(Color.DKGRAY);
+ canvas.drawPath(mRoundedClipPath, mDebugPaint);
+
int y = 0;
drawDebugInfo(canvas, y, Color.RED, /* label= */ "y = " + y);
@@ -843,14 +847,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
drawDebugInfo(canvas, y, Color.LTGRAY,
/* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
- y = (int) mAmbientState.getStackY() + mContentHeight;
- drawDebugInfo(canvas, y, Color.MAGENTA,
- /* label= */ "mAmbientState.getStackY() + mContentHeight = " + y);
-
y = (int) (mAmbientState.getStackY() + mIntrinsicContentHeight);
drawDebugInfo(canvas, y, Color.YELLOW,
/* label= */ "mAmbientState.getStackY() + mIntrinsicContentHeight = " + y);
+ y = mContentHeight;
+ drawDebugInfo(canvas, y, Color.MAGENTA,
+ /* label= */ "mContentHeight = " + y);
+
drawDebugInfo(canvas, mRoundedRectClippingBottom, Color.DKGRAY,
/* label= */ "mRoundedRectClippingBottom) = " + y);
}
@@ -4940,6 +4944,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
println(pw, "intrinsicPadding", mIntrinsicPadding);
println(pw, "topPadding", mTopPadding);
println(pw, "bottomPadding", mBottomPadding);
+ dumpRoundedRectClipping(pw);
+ println(pw, "requestedClipBounds", mRequestedClipBounds);
+ println(pw, "isClipped", mIsClipped);
println(pw, "translationX", getTranslationX());
println(pw, "translationY", getTranslationY());
println(pw, "translationZ", getTranslationZ());
@@ -4994,6 +5001,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
});
}
+ private void dumpRoundedRectClipping(IndentingPrintWriter pw) {
+ pw.append("roundedRectClipping{l=").print(mRoundedRectClippingLeft);
+ pw.append(" t=").print(mRoundedRectClippingTop);
+ pw.append(" r=").print(mRoundedRectClippingRight);
+ pw.append(" b=").print(mRoundedRectClippingBottom);
+ pw.append("} topRadius=").print(mBgCornerRadii[0]);
+ pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
+ }
+
private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
FooterViewRefactor.assertInLegacyMode();
final boolean showDismissView = shouldShowDismissView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6553193fc980..8ed1ca28eaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1260,9 +1260,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setQsFullScreen(boolean fullScreen) {
- FooterViewRefactor.assertInLegacyMode();
mView.setQsFullScreen(fullScreen);
- updateShowEmptyShadeView();
+ if (!FooterViewRefactor.isEnabled()) {
+ updateShowEmptyShadeView();
+ }
}
public void setScrollingEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 9efe632f5dbb..9fffb66ac831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.notification.stack.data.repository
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.StackRounding
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,7 +27,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
@SysUISingleton
class NotificationStackAppearanceRepository @Inject constructor() {
/** The bounds of the notification stack in the current scene. */
- val stackBounds = MutableStateFlow(NotificationContainerBounds())
+ val stackBounds = MutableStateFlow(StackBounds())
+
+ /** The whether the corners of the notification stack should be rounded */
+ // TODO: replace with the logic from QSController
+ val stackRounding = MutableStateFlow(StackRounding(roundTop = true, roundBottom = false))
/**
* The height in px of the contents of notification stack. Depending on the number of
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 08df47388556..5a56ca1444dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -17,9 +17,10 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.StackRounding
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -33,7 +34,10 @@ constructor(
private val repository: NotificationStackAppearanceRepository,
) {
/** The bounds of the notification stack in the current scene. */
- val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
+ val stackBounds: StateFlow<StackBounds> = repository.stackBounds.asStateFlow()
+
+ /** The rounding of the notification stack. */
+ val stackRounding: StateFlow<StackRounding> = repository.stackRounding.asStateFlow()
/**
* The height in px of the contents of notification stack. Depending on the number of
@@ -59,7 +63,7 @@ constructor(
val syntheticScroll: Flow<Float> = repository.syntheticScroll.asStateFlow()
/** Sets the position of the notification stack in the current scene. */
- fun setStackBounds(bounds: NotificationContainerBounds) {
+ fun setStackBounds(bounds: StackBounds) {
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
repository.stackBounds.value = bounds
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackBounds.kt
new file mode 100644
index 000000000000..1fc9a182a10c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackBounds.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.shared.model
+
+/** Models the bounds of the notification stack. */
+data class StackBounds(
+ /** The position of the left of the stack in its window coordinate system, in pixels. */
+ val left: Float = 0f,
+ /** The position of the top of the stack in its window coordinate system, in pixels. */
+ val top: Float = 0f,
+ /** The position of the right of the stack in its window coordinate system, in pixels. */
+ val right: Float = 0f,
+ /** The position of the bottom of the stack in its window coordinate system, in pixels. */
+ val bottom: Float = 0f,
+) {
+ /** The current height of the notification container. */
+ val height: Float = bottom - top
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackClipping.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackClipping.kt
new file mode 100644
index 000000000000..0c92b5023d1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackClipping.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.shared.model
+
+/** Models the clipping rounded rectangle of the notification stack */
+data class StackClipping(val bounds: StackBounds, val rounding: StackRounding)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackRounding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackRounding.kt
new file mode 100644
index 000000000000..ddc5d7ea0d7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/StackRounding.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.shared.model
+
+/** Models the corner rounds of the notification stack. */
+data class StackRounding(
+ /** Whether the top corners of the notification stack should be rounded. */
+ val roundTop: Boolean = false,
+ /** Whether the bottom corners of the notification stack should be rounded. */
+ val roundBottom: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index f10e5f1ab022..189c5e03ce07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -47,16 +47,17 @@ object NotificationStackAppearanceViewBinder {
return view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- viewModel.stackBounds.collect { bounds ->
+ viewModel.stackClipping.collect { (bounds, rounding) ->
val viewLeft = controller.view.left
val viewTop = controller.view.top
+ val roundRadius = SCRIM_CORNER_RADIUS.dpToPx(context)
controller.setRoundedClippingBounds(
bounds.left.roundToInt() - viewLeft,
bounds.top.roundToInt() - viewTop,
bounds.right.roundToInt() - viewLeft,
bounds.bottom.roundToInt() - viewTop,
- SCRIM_CORNER_RADIUS.dpToPx(context),
- 0,
+ if (rounding.roundTop) roundRadius else 0,
+ if (rounding.roundBottom) roundRadius else 0,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index c85a18a8a896..4744fcbbc7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -33,10 +34,12 @@ import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -55,6 +58,7 @@ constructor(
shadeInteractor: ShadeInteractor,
userSetupInteractor: UserSetupInteractor,
zenModeInteractor: ZenModeInteractor,
+ @Background bgDispatcher: CoroutineDispatcher,
) {
/**
* We want the NSSL to be unimportant for accessibility when there are no notifications in it
@@ -72,6 +76,7 @@ constructor(
) { hasNotifications, isShowingOnLockscreen ->
hasNotifications || !isShowingOnLockscreen
}
+ .flowOn(bgDispatcher)
.distinctUntilChanged()
}
}
@@ -95,6 +100,7 @@ constructor(
else -> true
}
}
+ .flowOn(bgDispatcher)
.distinctUntilChanged()
}
}
@@ -107,15 +113,13 @@ constructor(
activeNotificationsInteractor.areAnyNotificationsPresent,
userSetupInteractor.isUserSetUp,
notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.qsExpansion,
shadeInteractor.isQsFullscreen,
remoteInputInteractor.isRemoteInputActive,
- shadeInteractor.shadeExpansion.map { it == 0f }
+ shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged(),
) {
hasNotifications,
isUserSetUp,
isShowingOnLockscreen,
- qsExpansion,
qsFullScreen,
isRemoteInputActive,
isShadeClosed ->
@@ -131,7 +135,7 @@ constructor(
isShowingOnLockscreen -> VisibilityChange.HIDE_WITHOUT_ANIMATION
// Do not show the footer if quick settings are fully expanded (except
// for the foldable split shade view). See b/201427195 && b/222699879.
- qsExpansion == 1f && qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION
+ qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION
// Hide the footer if remote input is active (i.e. user is replying to a
// notification). See b/75984847.
isRemoteInputActive -> VisibilityChange.HIDE_WITH_ANIMATION
@@ -140,6 +144,7 @@ constructor(
else -> VisibilityChange.SHOW_WITH_ANIMATION
}
}
+ .flowOn(bgDispatcher)
.distinctUntilChanged(
// Equivalent unless visibility changes
areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index b6167e1ef0fb..a7cbc3374a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -18,7 +18,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -27,6 +26,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.Scenes.Shade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.StackClipping
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -83,8 +83,13 @@ constructor(
.dumpWhileCollecting("expandFraction")
/** The bounds of the notification stack in the current scene. */
- val stackBounds: Flow<NotificationContainerBounds> =
- stackAppearanceInteractor.stackBounds.dumpValue("stackBounds")
+ val stackClipping: Flow<StackClipping> =
+ combine(
+ stackAppearanceInteractor.stackBounds,
+ stackAppearanceInteractor.stackRounding,
+ ::StackClipping
+ )
+ .dumpWhileCollecting("stackClipping")
/** The y-coordinate in px of top of the contents of the notification stack. */
val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop.dumpValue("contentTop")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 9e2497d5bb41..ed44f20868b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -24,8 +24,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.StackBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.StackRounding
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -61,12 +64,17 @@ constructor(
right: Float,
bottom: Float,
) {
- val notificationContainerBounds =
- NotificationContainerBounds(top = top, bottom = bottom, left = left, right = right)
- keyguardInteractor.setNotificationContainerBounds(notificationContainerBounds)
- interactor.setStackBounds(notificationContainerBounds)
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = top, bottom = bottom)
+ )
+ interactor.setStackBounds(
+ StackBounds(top = top, bottom = bottom, left = left, right = right)
+ )
}
+ /** Corner rounding of the stack */
+ val stackRounding: StateFlow<StackRounding> = interactor.stackRounding
+
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 0db5c64c4c4e..665fc0aab316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -537,7 +537,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
@VisibleForTesting
void vibrateOnNavigationKeyDown() {
- mShadeViewController.performHapticFeedback(
+ mShadeController.performHapticFeedback(
HapticFeedbackConstants.GESTURE_START
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 25e634a03ef7..82b10bc11cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -20,7 +20,6 @@ import android.content.res.Configuration
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.annotation.StringRes
import com.android.keyguard.LockIconViewController
@@ -148,16 +147,6 @@ constructor(
return false
}
- override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
- val bottom = insets.displayCutout?.safeInsetBottom ?: 0
- if (isPaddingRelative) {
- setPaddingRelative(paddingStart, paddingTop, paddingEnd, bottom)
- } else {
- setPadding(paddingLeft, paddingTop, paddingRight, bottom)
- }
- return insets
- }
-
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
findViewById<View>(R.id.ambient_indication_container)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index ac1d2803835a..e977014e00f2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -91,7 +91,8 @@ constructor(
)
controller.init()
- applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ val bgDispatcher = bgHandler.asCoroutineDispatcher("@UnfoldBg Handler")
+ applicationScope.launch(bgDispatcher) {
powerInteractor.screenPowerState.collect {
if (it == ScreenPowerState.SCREEN_ON) {
readyCallback = null
@@ -99,7 +100,7 @@ constructor(
}
}
- applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+ applicationScope.launch(bgDispatcher) {
deviceStateRepository.state
.map { it == DeviceStateRepository.DeviceState.FOLDED }
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index d134e60ef72f..155102c9b9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -21,7 +21,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
-import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -52,13 +51,6 @@ interface MediaDevicesModule {
@Provides
@SysUISingleton
- fun provideLocalMediaInteractor(
- repository: LocalMediaRepository,
- @Application scope: CoroutineScope,
- ): LocalMediaInteractor = LocalMediaInteractor(repository, scope)
-
- @Provides
- @SysUISingleton
fun provideMediaDeviceSessionRepository(
intentsReceiver: AudioManagerEventsReceiver,
mediaSessionManager: MediaSessionManager,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 8ff2837c44ef..46ea38239aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -38,7 +38,8 @@ constructor(
fun onSettingsClicked() {
volumePanelViewModel.dismissPanel()
activityStarter.startActivity(
- Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ Intent(Settings.ACTION_SOUND_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
true,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 11b4690e59ee..e052f243f7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -15,15 +15,12 @@
*/
package com.android.systemui.volume.panel.component.mediaoutput.data.repository
-import android.media.MediaRouter2Manager
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
interface LocalMediaRepositoryFactory {
@@ -35,18 +32,14 @@ class LocalMediaRepositoryFactoryImpl
@Inject
constructor(
private val eventsReceiver: AudioManagerEventsReceiver,
- private val mediaRouter2Manager: MediaRouter2Manager,
private val localMediaManagerFactory: LocalMediaManagerFactory,
@Application private val coroutineScope: CoroutineScope,
- @Background private val backgroundCoroutineContext: CoroutineContext,
) : LocalMediaRepositoryFactory {
override fun create(packageName: String?): LocalMediaRepository =
LocalMediaRepositoryImpl(
eventsReceiver,
localMediaManagerFactory.create(packageName),
- mediaRouter2Manager,
coroutineScope,
- backgroundCoroutineContext,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
new file mode 100644
index 000000000000..b0c8a4a2d478
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.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.volume.panel.component.mediaoutput.domain.interactor
+
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.os.Handler
+import com.android.settingslib.volume.data.repository.MediaControllerChange
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.settingslib.volume.data.repository.stateChanges
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Allows to observe and change [MediaDeviceSession] state. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaDeviceSessionInteractor
+@Inject
+constructor(
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ @Background private val backgroundHandler: Handler,
+ private val mediaControllerRepository: MediaControllerRepository,
+) {
+
+ /** [PlaybackState] changes for the [MediaDeviceSession]. */
+ fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> {
+ return stateChanges(session) {
+ emit(MediaControllerChange.PlaybackStateChanged(it.playbackState))
+ }
+ .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class)
+ .map { it.state }
+ }
+
+ /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
+ fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
+ return stateChanges(session) {
+ emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo))
+ }
+ .filterIsInstance(MediaControllerChange.AudioInfoChanged::class)
+ .map { it.info }
+ }
+
+ private fun stateChanges(
+ session: MediaDeviceSession,
+ onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit,
+ ): Flow<MediaControllerChange?> =
+ mediaControllerRepository.activeSessions
+ .flatMapLatest { controllers ->
+ val controller: MediaController =
+ findControllerForSession(controllers, session)
+ ?: return@flatMapLatest flowOf(null)
+ controller.stateChanges(backgroundHandler).onStart { onStart(controller) }
+ }
+ .flowOn(backgroundCoroutineContext)
+
+ /** Set [MediaDeviceSession] volume to [volume]. */
+ suspend fun setSessionVolume(mediaDeviceSession: MediaDeviceSession, volume: Int): Boolean {
+ if (!mediaDeviceSession.canAdjustVolume) {
+ return false
+ }
+ return withContext(backgroundCoroutineContext) {
+ val controller =
+ findControllerForSession(
+ mediaControllerRepository.activeSessions.value,
+ mediaDeviceSession,
+ )
+ if (controller == null) {
+ false
+ } else {
+ controller.setVolumeTo(volume, 0)
+ true
+ }
+ }
+ }
+
+ private fun findControllerForSession(
+ controllers: Collection<MediaController>,
+ mediaDeviceSession: MediaDeviceSession,
+ ): MediaController? =
+ controllers.firstOrNull { it.sessionToken == mediaDeviceSession.sessionToken }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index cb16abe7e575..ea4c082f4660 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -33,23 +33,15 @@ constructor(
private val mediaOutputDialogManager: MediaOutputDialogManager,
) {
- fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
- when (session) {
- is MediaDeviceSession.Active -> {
- mediaOutputDialogManager.createAndShowWithController(
- session.packageName,
- false,
- expandable.dialogController()
- )
- }
- is MediaDeviceSession.Inactive -> {
- mediaOutputDialogManager.createAndShowForSystemRouting(
- expandable.dialogController()
- )
- }
- else -> {
- /* do nothing */
- }
+ fun onBarClick(session: MediaDeviceSession, isPlaybackActive: Boolean, expandable: Expandable) {
+ if (isPlaybackActive) {
+ mediaOutputDialogManager.createAndShowWithController(
+ session.packageName,
+ false,
+ expandable.dialogController()
+ )
+ } else {
+ mediaOutputDialogManager.createAndShowForSystemRouting(expandable.dialogController())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 0f5343701ac6..e60139ecf9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -17,17 +17,16 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import android.content.pm.PackageManager
+import android.media.VolumeProvider
import android.media.session.MediaController
-import android.os.Handler
import android.util.Log
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
-import com.android.settingslib.volume.data.repository.MediaControllerChange
import com.android.settingslib.volume.data.repository.MediaControllerRepository
-import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -38,12 +37,9 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -58,35 +54,40 @@ constructor(
private val packageManager: PackageManager,
@VolumePanelScope private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
- @Background private val backgroundHandler: Handler,
- mediaControllerRepository: MediaControllerRepository
+ mediaControllerRepository: MediaControllerRepository,
) {
- /** Current [MediaDeviceSession]. Emits when the session playback changes. */
- val mediaDeviceSession: StateFlow<MediaDeviceSession> =
- mediaControllerRepository.activeLocalMediaController
- .flatMapLatest { it?.mediaDeviceSession() ?: flowOf(MediaDeviceSession.Inactive) }
- .flowOn(backgroundCoroutineContext)
- .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSession.Inactive)
+ private val activeMediaControllers: Flow<MediaControllers> =
+ mediaControllerRepository.activeSessions
+ .map { getMediaControllers(it) }
+ .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1)
+
+ /** [MediaDeviceSessions] that contains currently active sessions. */
+ val activeMediaDeviceSessions: Flow<MediaDeviceSessions> =
+ activeMediaControllers.map {
+ MediaDeviceSessions(
+ local = it.local?.mediaDeviceSession(),
+ remote = it.remote?.mediaDeviceSession()
+ )
+ }
- private fun MediaController.mediaDeviceSession(): Flow<MediaDeviceSession> {
- return stateChanges(backgroundHandler)
- .onStart { emit(MediaControllerChange.PlaybackStateChanged(playbackState)) }
- .filterIsInstance<MediaControllerChange.PlaybackStateChanged>()
+ /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
+ val defaultActiveMediaSession: StateFlow<MediaDeviceSession?> =
+ activeMediaControllers
.map {
- MediaDeviceSession.Active(
- appLabel = getApplicationLabel(packageName)
- ?: return@map MediaDeviceSession.Inactive,
- packageName = packageName,
- sessionToken = sessionToken,
- playbackState = playbackState,
- )
+ when {
+ it.local?.playbackState?.isActive == true -> it.local.mediaDeviceSession()
+ it.remote?.playbackState?.isActive == true -> it.remote.mediaDeviceSession()
+ it.local != null -> it.local.mediaDeviceSession()
+ else -> null
+ }
}
- }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
private val localMediaRepository: SharedFlow<LocalMediaRepository> =
- mediaDeviceSession
- .map { (it as? MediaDeviceSession.Active)?.packageName }
+ defaultActiveMediaSession
+ .map { it?.packageName }
.distinctUntilChanged()
.map { localMediaRepositoryFactory.create(it) }
.shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1)
@@ -111,6 +112,54 @@ constructor(
}
}
+ /** Finds local and remote media controllers. */
+ private fun getMediaControllers(
+ controllers: Collection<MediaController>,
+ ): MediaControllers {
+ var localController: MediaController? = null
+ var remoteController: MediaController? = null
+ val remoteMediaSessions: MutableSet<String> = mutableSetOf()
+ for (controller in controllers) {
+ val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+ when (playbackInfo.playbackType) {
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+ // MediaController can't be local if there is a remote one for the same package
+ if (localController?.packageName.equals(controller.packageName)) {
+ localController = null
+ }
+ if (!remoteMediaSessions.contains(controller.packageName)) {
+ remoteMediaSessions.add(controller.packageName)
+ if (remoteController == null) {
+ remoteController = controller
+ }
+ }
+ }
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+ if (controller.packageName in remoteMediaSessions) continue
+ if (localController != null) continue
+ localController = controller
+ }
+ }
+ }
+ return MediaControllers(local = localController, remote = remoteController)
+ }
+
+ private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+ return MediaDeviceSession(
+ packageName = packageName,
+ sessionToken = sessionToken,
+ canAdjustVolume =
+ playbackInfo != null &&
+ playbackInfo?.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
+ appLabel = getApplicationLabel(packageName) ?: return null
+ )
+ }
+
+ private data class MediaControllers(
+ val local: MediaController?,
+ val remote: MediaController?,
+ )
+
private companion object {
const val TAG = "MediaOutputInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 1bceee9b2d34..2a2ce796a2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -17,26 +17,15 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.model
import android.media.session.MediaSession
-import android.media.session.PlaybackState
/** Represents media playing on the connected device. */
-sealed interface MediaDeviceSession {
+data class MediaDeviceSession(
+ val appLabel: CharSequence,
+ val packageName: String,
+ val sessionToken: MediaSession.Token,
+ val canAdjustVolume: Boolean,
+)
- /** Media is playing. */
- data class Active(
- val appLabel: CharSequence,
- val packageName: String,
- val sessionToken: MediaSession.Token,
- val playbackState: PlaybackState?,
- ) : MediaDeviceSession
-
- /** Media is not playing. */
- data object Inactive : MediaDeviceSession
-
- /** Current media state is unknown yet. */
- data object Unknown : MediaDeviceSession
-}
-
-/** Returns true when the audio is playing for the [MediaDeviceSession]. */
-fun MediaDeviceSession.isPlaying(): Boolean =
- this is MediaDeviceSession.Active && playbackState?.isActive == true
+/** Returns true when [other] controls the same sessions as [this]. */
+fun MediaDeviceSession.isTheSameSession(other: MediaDeviceSession?): Boolean =
+ sessionToken == other?.sessionToken
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
new file mode 100644
index 000000000000..ddc078421b9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+
+/** Models a pair of local and remote [MediaDeviceSession]s. */
+data class MediaDeviceSessions(
+ val local: MediaDeviceSession?,
+ val remote: MediaDeviceSession?,
+) {
+
+ companion object {
+ /** Returns [MediaDeviceSessions.local]. */
+ val Local: (MediaDeviceSessions) -> MediaDeviceSession? = { it.local }
+ /** Returns [MediaDeviceSessions.remote]. */
+ val Remote: (MediaDeviceSessions) -> MediaDeviceSession? = { it.remote }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index d49cb1ea6958..2530a3a46384 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,24 +17,30 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import android.media.session.PlaybackState
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Models the UI of the Media Output Volume Panel component. */
+@OptIn(ExperimentalCoroutinesApi::class)
@VolumePanelScope
class MediaOutputViewModel
@Inject
@@ -43,25 +49,36 @@ constructor(
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val volumePanelViewModel: VolumePanelViewModel,
private val actionsInteractor: MediaOutputActionsInteractor,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
interactor: MediaOutputInteractor,
) {
- private val mediaDeviceSession: StateFlow<MediaDeviceSession> =
- interactor.mediaDeviceSession.stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- MediaDeviceSession.Unknown,
- )
+ private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+ interactor.defaultActiveMediaSession
+ .flatMapLatest { session ->
+ if (session == null) {
+ flowOf(null)
+ } else {
+ mediaDeviceSessionInteractor.playbackState(session).map { playback ->
+ playback?.let { SessionWithPlayback(session, it) }
+ }
+ }
+ }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.Eagerly,
+ null,
+ )
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
- combine(mediaDeviceSession, interactor.currentConnectedDevice) {
+ combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
ConnectedDeviceViewModel(
- if (mediaDeviceSession.isPlaying()) {
+ if (mediaDeviceSession?.playback?.isActive == true) {
context.getString(
R.string.media_output_label_title,
- (mediaDeviceSession as MediaDeviceSession.Active).appLabel
+ mediaDeviceSession.session.appLabel
)
} else {
context.getString(R.string.media_output_title_without_playing)
@@ -76,10 +93,10 @@ constructor(
)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
- combine(mediaDeviceSession, interactor.currentConnectedDevice) {
+ combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession.isPlaying()) {
+ if (mediaDeviceSession?.playback?.isActive == true) {
val icon =
currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
?: Icon.Resource(
@@ -112,7 +129,14 @@ constructor(
)
fun onBarClick(expandable: Expandable) {
- actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
+ sessionWithPlayback.value?.let {
+ actionsInteractor.onBarClick(it.session, it.playback.isActive, expandable)
+ }
volumePanelViewModel.dismissPanel()
}
+
+ private data class SessionWithPlayback(
+ val session: MediaDeviceSession,
+ val playback: PlaybackState,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
deleted file mode 100644
index 6b62074e023d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/CastVolumeInteractor.kt
+++ /dev/null
@@ -1,48 +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.volume.panel.component.volume.domain.interactor
-
-import com.android.settingslib.volume.domain.interactor.LocalMediaInteractor
-import com.android.settingslib.volume.domain.model.RoutingSession
-import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Provides a remote media casting state. */
-@VolumePanelScope
-class CastVolumeInteractor
-@Inject
-constructor(
- @VolumePanelScope private val coroutineScope: CoroutineScope,
- private val localMediaInteractor: LocalMediaInteractor,
-) {
-
- /** Returns a list of [RoutingSession] to show in the UI. */
- val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
- localMediaInteractor.remoteRoutingSessions
- .map { it.filter { routingSession -> routingSession.isVolumeSeekBarEnabled } }
- .stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
-
- /** Sets [routingSession] volume to [volume]. */
- suspend fun setVolume(routingSession: RoutingSession, volume: Int) {
- localMediaInteractor.adjustSessionVolume(routingSession.routingSessionInfo.id, volume)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 1b732081a12a..d49442c149ee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -80,7 +80,7 @@ constructor(
) { model, isEnabled, ringerMode ->
model.toState(isEnabled, ringerMode)
}
- .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
@@ -163,17 +163,6 @@ constructor(
val audioStreamModel: AudioStreamModel,
) : SliderState
- private data object EmptyState : SliderState {
- override val value: Float = 0f
- override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
- override val icon: Icon? = null
- override val valueText: String = ""
- override val label: String = ""
- override val disabledMessage: String? = null
- override val a11yStep: Int = 0
- override val isEnabled: Boolean = true
- }
-
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 86b2d73de3e3..0f240b37f02e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -17,11 +17,11 @@
package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
-import com.android.settingslib.volume.domain.model.RoutingSession
+import android.media.session.MediaController.PlaybackInfo
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -30,30 +30,29 @@ import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class CastVolumeSliderViewModel
@AssistedInject
constructor(
- @Assisted private val routingSession: RoutingSession,
+ @Assisted private val session: MediaDeviceSession,
@Assisted private val coroutineScope: CoroutineScope,
private val context: Context,
- mediaOutputInteractor: MediaOutputInteractor,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val volumeSliderInteractor: VolumeSliderInteractor,
- private val castVolumeInteractor: CastVolumeInteractor,
) : SliderViewModel {
- private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
-
override val slider: StateFlow<SliderState> =
- combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
- .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
+ mediaDeviceSessionInteractor
+ .playbackInfo(session)
+ .mapNotNull { it?.getCurrentState() }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
+ mediaDeviceSessionInteractor.setSessionVolume(session, newValue.roundToInt())
}
}
@@ -61,15 +60,16 @@ constructor(
// do nothing because this action isn't supported for Cast sliders.
}
- private fun getCurrentState(): State =
- State(
- value = routingSession.routingSessionInfo.volume.toFloat(),
+ private fun PlaybackInfo.getCurrentState(): State {
+ val volumeRange = 0..maxVolume
+ return State(
+ value = currentVolume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = Icon.Resource(R.drawable.ic_cast, null),
valueText =
SliderViewModel.formatValue(
volumeSliderInteractor.processVolumeToValue(
- volume = routingSession.routingSessionInfo.volume,
+ volume = currentVolume,
volumeRange = volumeRange,
)
),
@@ -77,6 +77,7 @@ constructor(
isEnabled = true,
a11yStep = 1
)
+ }
private data class State(
override val value: Float,
@@ -95,7 +96,7 @@ constructor(
interface Factory {
fun create(
- routingSession: RoutingSession,
+ session: MediaDeviceSession,
coroutineScope: CoroutineScope,
): CastVolumeSliderViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index b87d0a786740..3dca2724b095 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -36,4 +36,15 @@ sealed interface SliderState {
*/
val a11yStep: Int
val disabledMessage: String?
+
+ data object Empty : SliderState {
+ override val value: Float = 0f
+ override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
+ override val icon: Icon? = null
+ override val valueText: String = ""
+ override val label: String = ""
+ override val disabledMessage: String? = null
+ override val a11yStep: Int = 0
+ override val isEnabled: Boolean = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index aaee24b9357f..4e9a45635f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,9 +18,10 @@ package com.android.systemui.volume.panel.component.volume.ui.viewmodel
import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
-import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isTheSameSession
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -29,17 +30,15 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/**
@@ -52,50 +51,34 @@ class AudioVolumeComponentViewModel
@Inject
constructor(
@VolumePanelScope private val scope: CoroutineScope,
- castVolumeInteractor: CastVolumeInteractor,
mediaOutputInteractor: MediaOutputInteractor,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
) {
- private val remoteSessionsViewModels: Flow<List<SliderViewModel>> =
- castVolumeInteractor.remoteRoutingSessions.transformLatest { routingSessions ->
- coroutineScope {
- emit(
- routingSessions.map { routingSession ->
- castVolumeSliderViewModelFactory.create(routingSession, this)
- }
- )
- }
- }
- private val streamViewModels: Flow<List<SliderViewModel>> =
- flowOf(
- listOf(
- AudioStream(AudioManager.STREAM_MUSIC),
- AudioStream(AudioManager.STREAM_VOICE_CALL),
- AudioStream(AudioManager.STREAM_RING),
- AudioStream(AudioManager.STREAM_NOTIFICATION),
- AudioStream(AudioManager.STREAM_ALARM),
- )
- )
- .transformLatest { streams ->
+ val sliderViewModels: StateFlow<List<SliderViewModel>> =
+ combineTransform(
+ mediaOutputInteractor.activeMediaDeviceSessions,
+ mediaOutputInteractor.defaultActiveMediaSession,
+ ) { activeSessions, defaultSession ->
coroutineScope {
- emit(
- streams.map { stream ->
- streamSliderViewModelFactory.create(
- AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
- this,
- )
+ val viewModels = buildList {
+ if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
+ addRemoteViewModelIfNeeded(this, activeSessions.remote)
+ addStreamViewModel(this, AudioManager.STREAM_MUSIC)
+ } else {
+ addStreamViewModel(this, AudioManager.STREAM_MUSIC)
+ addRemoteViewModelIfNeeded(this, activeSessions.remote)
}
- )
- }
- }
- val sliderViewModels: StateFlow<List<SliderViewModel>> =
- combine(remoteSessionsViewModels, streamViewModels) {
- remoteSessionsViewModels,
- streamViewModels ->
- remoteSessionsViewModels + streamViewModels
+ addStreamViewModel(this, AudioManager.STREAM_VOICE_CALL)
+ addStreamViewModel(this, AudioManager.STREAM_RING)
+ addStreamViewModel(this, AudioManager.STREAM_NOTIFICATION)
+ addStreamViewModel(this, AudioManager.STREAM_ALARM)
+ }
+ emit(viewModels)
+ }
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
@@ -103,12 +86,41 @@ constructor(
val isExpanded: StateFlow<Boolean> =
merge(
- mutableIsExpanded.onStart { emit(false) },
- mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+ mutableIsExpanded,
+ mediaOutputInteractor.defaultActiveMediaSession.flatMapLatest {
+ if (it == null) flowOf(true)
+ else mediaDeviceSessionInteractor.playbackState(it).map { it?.isActive != true }
+ },
)
.stateIn(scope, SharingStarted.Eagerly, false)
fun onExpandedChanged(isExpanded: Boolean) {
scope.launch { mutableIsExpanded.emit(isExpanded) }
}
+
+ private fun CoroutineScope.addRemoteViewModelIfNeeded(
+ list: MutableList<SliderViewModel>,
+ remoteMediaDeviceSession: MediaDeviceSession?
+ ) {
+ if (remoteMediaDeviceSession?.canAdjustVolume == true) {
+ val viewModel =
+ castVolumeSliderViewModelFactory.create(
+ remoteMediaDeviceSession,
+ this,
+ )
+ list.add(viewModel)
+ }
+ }
+
+ private fun CoroutineScope.addStreamViewModel(
+ list: MutableList<SliderViewModel>,
+ stream: Int,
+ ) {
+ val viewModel =
+ streamSliderViewModelFactory.create(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
+ this,
+ )
+ list.add(viewModel)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
index 206babf9ec44..09675e28f5da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
+import androidx.lifecycle.ViewModel;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -56,7 +57,8 @@ public class ComplicationViewModelTransformerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent);
when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider);
- when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel);
+ when(mViewModelProvider.get(Mockito.any(), Mockito.<Class<ViewModel>>any()))
+ .thenReturn(mViewModel);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 66fdf538e284..933ddb5739e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -16,25 +16,22 @@
package com.android.systemui.haptics.slider
-import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.view.VelocityTracker
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
import kotlin.math.max
import kotlin.test.assertEquals
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -42,10 +39,10 @@ import org.mockito.MockitoAnnotations
class SliderHapticFeedbackProviderTest : SysuiTestCase() {
@Mock private lateinit var velocityTracker: VelocityTracker
- @Mock private lateinit var vibratorHelper: VibratorHelper
+
+ private val kosmos = testKosmos()
private val config = SliderHapticFeedbackConfig()
- private val clock = FakeSystemClock()
private val lowTickDuration = 12 // Mocked duration of a low tick
private val dragTextureThresholdMillis =
@@ -55,250 +52,278 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(vibratorHelper.getPrimitiveDurations(any()))
- .thenReturn(intArrayOf(lowTickDuration))
whenever(velocityTracker.isAxisSupported(config.velocityAxis)).thenReturn(true)
whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
.thenReturn(config.maxVelocityToScale)
+
+ kosmos.vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
+ lowTickDuration
sliderHapticFeedbackProvider =
- SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, config, clock)
+ SliderHapticFeedbackProvider(
+ kosmos.vibratorHelper,
+ velocityTracker,
+ config,
+ kosmos.fakeSystemClock,
+ )
}
@Test
- fun playHapticAtLowerBookend_playsClick() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- sliderHapticFeedbackProvider.onLowerBookend()
-
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtLowerBookend_playsClick() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
@Test
- fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
- )
- .compose()
-
- sliderHapticFeedbackProvider.onLowerBookend()
- sliderHapticFeedbackProvider.onLowerBookend()
-
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onLowerBookend()
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
@Test
- fun playHapticAtUpperBookend_playsClick() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- sliderHapticFeedbackProvider.onUpperBookend()
+ fun playHapticAtUpperBookend_playsClick() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ @Test
+ fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onUpperBookend()
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(vibration))
+ }
@Test
- fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+ fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
)
- .compose()
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
- sliderHapticFeedbackProvider.onUpperBookend()
- sliderHapticFeedbackProvider.onUpperBookend()
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- verify(vibratorHelper, times(1))
- .vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ // WHEN two calls to play occur immediately
+ sliderHapticFeedbackProvider.onProgress(progress)
+ sliderHapticFeedbackProvider.onProgress(progress)
- @Test
- fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
- )
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ // THEN the correct composition only plays once
+ assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(ticks.compose()))
}
- // GIVEN system running for 1s
- clock.advanceTime(1000)
-
- // WHEN two calls to play occur immediately
- sliderHapticFeedbackProvider.onProgress(progress)
- sliderHapticFeedbackProvider.onProgress(progress)
-
- // THEN the correct composition only plays once
- verify(vibratorHelper, times(1))
- .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
- }
-
@Test
- fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
- // GIVEN max velocity and a slider progress at half progress
- val firstProgress = 0.5f
- val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
-
- // Given a second slider progress event smaller than the progress threshold
- val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
-
- // GIVEN system running for 1s
- clock.advanceTime(1000)
-
- // WHEN two calls to play occur with the required threshold separation (time and progress)
- sliderHapticFeedbackProvider.onProgress(firstProgress)
- clock.advanceTime(dragTextureThresholdMillis.toLong())
- sliderHapticFeedbackProvider.onProgress(secondProgress)
-
- // THEN Only the first compositions plays
- verify(vibratorHelper, times(1))
- .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
- verify(vibratorHelper, times(1))
- .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event smaller than the progress threshold
+ val secondProgress =
+ firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and
+ // progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN Only the first compositions plays
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+ assertEquals(/* expected= */ 1, vibratorHelper.totalVibrations)
+ }
@Test
- fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
- // GIVEN max velocity and a slider progress at half progress
- val firstProgress = 0.5f
- val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
-
- // Given a second slider progress event beyond progress threshold
- val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
- val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
-
- // GIVEN system running for 1s
- clock.advanceTime(1000)
-
- // WHEN two calls to play occur with the required threshold separation (time and progress)
- sliderHapticFeedbackProvider.onProgress(firstProgress)
- clock.advanceTime(dragTextureThresholdMillis.toLong())
- sliderHapticFeedbackProvider.onProgress(secondProgress)
-
- // THEN the correct compositions play
- verify(vibratorHelper, times(1))
- .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
- verify(vibratorHelper, times(1))
- .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val firstProgress = 0.5f
+ val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)
+
+ // Given a second slider progress event beyond progress threshold
+ val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
+ val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN two calls to play occur with the required threshold separation (time and
+ // progress)
+ sliderHapticFeedbackProvider.onProgress(firstProgress)
+ fakeSystemClock.advanceTime(dragTextureThresholdMillis.toLong())
+ sliderHapticFeedbackProvider.onProgress(secondProgress)
+
+ // THEN the correct compositions play
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(secondTicks))
+ }
@Test
- fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
+ fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
+ val bookendVibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
+ sliderHapticFeedbackProvider.onLowerBookend()
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN a vibration is to trigger again at the lower bookend
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ // THEN there are two bookend vibrations
+ assertEquals(
+ /* expected= */ 2,
+ vibratorHelper.timesVibratedWithEffect(bookendVibration)
)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
}
- val bookendVibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
- sliderHapticFeedbackProvider.onLowerBookend()
- sliderHapticFeedbackProvider.onProgress(progress)
-
- // WHEN a vibration is to trigger again at the lower bookend
- sliderHapticFeedbackProvider.onLowerBookend()
-
- // THEN there are two bookend vibrations
- verify(vibratorHelper, times(2))
- .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
- }
@Test
- fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
+ fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
+ val bookendVibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
+ sliderHapticFeedbackProvider.onUpperBookend()
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // WHEN a vibration is to trigger again at the upper bookend
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ // THEN there are two bookend vibrations
+ assertEquals(
+ /* expected= */ 2,
+ vibratorHelper.timesVibratedWithEffect(bookendVibration)
)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
}
- val bookendVibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
- sliderHapticFeedbackProvider.onUpperBookend()
- sliderHapticFeedbackProvider.onProgress(progress)
- // WHEN a vibration is to trigger again at the upper bookend
- sliderHapticFeedbackProvider.onUpperBookend()
+ fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
- // THEN there are two bookend vibrations
- verify(vibratorHelper, times(2))
- .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
- }
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
- // GIVEN max velocity and a slider progress at half progress
- val progress = 0.5f
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
- // GIVEN system running for 1s
- clock.advanceTime(1000)
-
- // WHEN a drag texture plays
- sliderHapticFeedbackProvider.onProgress(progress)
-
- // THEN the dragTextureLastProgress remembers the latest progress
- assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
- }
+ // THEN the dragTextureLastProgress remembers the latest progress
+ assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
@Test
- fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
- // GIVEN max velocity and a slider progress at half progress
- val progress = 0.5f
+ fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
- // GIVEN system running for 1s
- clock.advanceTime(1000)
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- // WHEN a drag texture plays
- sliderHapticFeedbackProvider.onProgress(progress)
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
- // WHEN the handle is released
- sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+ // WHEN the handle is released
+ sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
- // THEN the dragTextureLastProgress tracker is reset
- assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
- }
+ // THEN the dragTextureLastProgress tracker is reset
+ assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
val ticks = VibrationEffect.startComposition()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index a63b2211f71a..db0c0bcfa8f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -80,6 +80,8 @@ import android.app.people.IPeopleManager;
import android.app.people.PeopleManager;
import android.app.people.PeopleSpaceTile;
import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -101,11 +103,12 @@ import android.text.TextUtils;
import androidx.preference.PreferenceManager;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.people.SharedPreferencesHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.SbnBuilder;
@@ -265,6 +268,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
private final FakeExecutor mFakeExecutor = new FakeExecutor(mClock);
+ private final FakeUserTracker mUserTracker = new FakeUserTracker();
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -272,7 +277,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
- mNotificationManager, mFakeExecutor);
+ mNotificationManager, mFakeExecutor, mUserTracker);
mManager.attach(mListenerService);
verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -309,6 +314,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
.setId(1)
.setShortcutInfo(mShortcutInfo)
.build();
+
+ AppWidgetProviderInfo providerInfo = new AppWidgetProviderInfo();
+ providerInfo.provider = new ComponentName("com.android.systemui.tests",
+ "com.android.systemui.people.widget.PeopleSpaceWidgetProvider");
+ when(mAppWidgetManager.getInstalledProvidersForPackage(anyString(), any()))
+ .thenReturn(List.of(providerInfo));
}
@Test
@@ -1562,6 +1573,43 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
String.valueOf(WIDGET_ID_WITH_KEY_IN_OPTIONS));
}
+ @Test
+ public void testUpdateGeneratedPreview_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+ mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
+ }
+
+ @Test
+ public void testUpdateGeneratedPreview_userLocked() {
+ mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+ when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
+
+ mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
+ }
+
+ @Test
+ public void testUpdateGeneratedPreview_userUnlocked() {
+ mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+ when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
+ when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
+
+ mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any());
+ }
+
+ @Test
+ public void testUpdateGeneratedPreview_doesNotSetTwice() {
+ mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
+ when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
+ when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
+
+ mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
+ verify(mAppWidgetManager, times(1)).setWidgetPreview(any(), anyInt(), any());
+ }
+
private void setFinalField(String fieldName, int value) {
try {
Field field = NotificationManager.Policy.class.getDeclaredField(fieldName);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index cc48640b15bc..5c6ed70c85a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -21,6 +21,7 @@ import android.testing.TestableLooper.RunWithLooper
import android.testing.ViewUtils
import android.view.ContextThemeWrapper
import android.view.View
+import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.FrameLayout
@@ -71,7 +72,7 @@ class QSPanelTest : SysuiTestCase() {
qsPanel = QSPanel(themedContext, null)
qsPanel.mUsingMediaPlayer = true
- qsPanel.initialize(qsLogger)
+ qsPanel.initialize(qsLogger, true)
// QSPanel inflates a footer inside of it, mocking it here
footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
qsPanel.addView(footer, MATCH_PARENT, 100)
@@ -218,6 +219,62 @@ class QSPanelTest : SysuiTestCase() {
verify(tile).addCallback(record.callback)
}
+ @Test
+ fun initializedWithNoMedia_tileLayoutParentIsAlwaysQsPanel() {
+ lateinit var panel: QSPanel
+ lateinit var tileLayout: View
+ testableLooper.runWithLooper {
+ panel = QSPanel(themedContext, null)
+ panel.mUsingMediaPlayer = true
+
+ panel.initialize(qsLogger, /* usingMediaPlayer= */ false)
+ tileLayout = panel.orCreateTileLayout as View
+ // QSPanel inflates a footer inside of it, mocking it here
+ footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
+ panel.addView(footer, MATCH_PARENT, 100)
+ panel.onFinishInflate()
+ // Provides a parent with non-zero size for QSPanel
+ ViewUtils.attachView(panel)
+ }
+ val mockMediaHost = mock(ViewGroup::class.java)
+
+ panel.setUsingHorizontalLayout(false, mockMediaHost, true)
+
+ assertThat(tileLayout.parent).isSameInstanceAs(panel)
+
+ panel.setUsingHorizontalLayout(true, mockMediaHost, true)
+ assertThat(tileLayout.parent).isSameInstanceAs(panel)
+
+ ViewUtils.detachView(panel)
+ }
+
+ @Test
+ fun initializeWithNoMedia_mediaNeverAttached() {
+ lateinit var panel: QSPanel
+ testableLooper.runWithLooper {
+ panel = QSPanel(themedContext, null)
+ panel.mUsingMediaPlayer = true
+
+ panel.initialize(qsLogger, /* usingMediaPlayer= */ false)
+ panel.orCreateTileLayout as View
+ // QSPanel inflates a footer inside of it, mocking it here
+ footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
+ panel.addView(footer, MATCH_PARENT, 100)
+ panel.onFinishInflate()
+ // Provides a parent with non-zero size for QSPanel
+ ViewUtils.attachView(panel)
+ }
+ val mockMediaHost = FrameLayout(themedContext)
+
+ panel.setUsingHorizontalLayout(false, mockMediaHost, true)
+ assertThat(mockMediaHost.parent).isNull()
+
+ panel.setUsingHorizontalLayout(true, mockMediaHost, true)
+ assertThat(mockMediaHost.parent).isNull()
+
+ ViewUtils.detachView(panel)
+ }
+
private infix fun View.isLeftOf(other: View): Boolean {
val rect = Rect()
getBoundsOnScreen(rect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
index 3fba3938db19..e5369fcae0b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
@@ -36,7 +36,7 @@ class QuickQSPanelTest : SysuiTestCase() {
testableLooper.runWithLooper {
quickQSPanel = QuickQSPanel(mContext, null)
- quickQSPanel.initialize(qsLogger)
+ quickQSPanel.initialize(qsLogger, true)
quickQSPanel.onFinishInflate()
// Provides a parent with non-zero size for QSPanel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index e0fff9c10873..04e214ac7a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tileimpl
import android.content.Context
import android.graphics.drawable.Drawable
+import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -27,6 +28,7 @@ import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
@@ -380,6 +382,34 @@ class QSTileViewImplTest : SysuiTestCase() {
assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
}
+ @Test
+ @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+ val state = QSTile.State() // A state that handles longPress
+
+ // GIVEN an invalid long-press effect duration
+ tileView.constantLongPressEffectDuration = -1
+
+ // WHEN the state changes
+ tileView.changeState(state)
+
+ // THEN the long-press effect is not created
+ assertThat(tileView.hasLongPressEffect).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+ // GIVEN a test state that handles long-press and a valid long-press effect duration
+ val state = QSTile.State()
+
+ // WHEN the state changes
+ tileView.changeState(state)
+
+ // THEN the long-press effect created
+ assertThat(tileView.hasLongPressEffect).isTrue()
+ }
+
class FakeTileView(
context: Context,
collapsed: Boolean
@@ -387,6 +417,9 @@ class QSTileViewImplTest : SysuiTestCase() {
ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
collapsed
) {
+ var constantLongPressEffectDuration = 500
+
+ override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
fun changeState(state: QSTile.State) {
handleStateChanged(state)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 10d6ebf11be7..1313227c7f3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -21,7 +21,7 @@ import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.PowerManager
-import android.os.Process;
+import android.os.Process
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
@@ -34,8 +34,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
@@ -96,7 +94,6 @@ class OverviewProxyServiceTest : SysuiTestCase() {
private val displayTracker = FakeDisplayTracker(mContext)
private val fakeSystemClock = FakeSystemClock()
private val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
- private val featureFlags = FakeFeatureFlags()
private val wakefulnessLifecycle =
WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@@ -121,8 +118,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Mock
private lateinit var unfoldTransitionProgressForwarder:
Optional<UnfoldTransitionProgressForwarder>
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
@@ -205,16 +201,14 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Test
fun connectToOverviewService_primaryUser_expectBindService() {
- val mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Process::class.java)
- .startMocking()
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
try {
`when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM)
val spyContext = spy(context)
val ops = createOverviewProxyService(spyContext)
ops.startConnectionToCurrentUser()
- verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(),
- anyInt(), any())
+ verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -222,22 +216,20 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Test
fun connectToOverviewService_nonPrimaryUser_expectNoBindService() {
- val mockitoSession = ExtendedMockito.mockitoSession()
- .spyStatic(Process::class.java)
- .startMocking()
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
try {
`when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
val spyContext = spy(context)
val ops = createOverviewProxyService(spyContext)
ops.startConnectionToCurrentUser()
- verify(spyContext, times(0)).bindServiceAsUser(any(), any(),
- anyInt(), any())
+ verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
} finally {
mockitoSession.finishMocking()
}
}
- private fun createOverviewProxyService(ctx: Context) : OverviewProxyService {
+ private fun createOverviewProxyService(ctx: Context): OverviewProxyService {
return OverviewProxyService(
ctx,
executor,
@@ -257,7 +249,6 @@ class OverviewProxyServiceTest : SysuiTestCase() {
sysuiUnlockAnimationController,
inWindowLauncherUnlockAnimationManager,
assistUtils,
- featureFlags,
FakeSceneContainerFlags(),
dumpManager,
unfoldTransitionProgressForwarder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5c40538586e..8e8dd4d91e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -188,7 +188,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
public void vibrateOnNavigationKeyDown_usesPerformHapticFeedback() {
mSbcqCallbacks.vibrateOnNavigationKeyDown();
- verify(mShadeViewController).performHapticFeedback(
+ verify(mShadeController).performHapticFeedback(
HapticFeedbackConstants.GESTURE_START
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
index 7c36a85243a2..7a83cfe852d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt
@@ -19,7 +19,9 @@ package com.android.systemui.surfaceeffects.loadingeffect
import android.graphics.Paint
import android.graphics.RenderEffect
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.model.SysUiStateTest
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN
@@ -31,18 +33,17 @@ import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class LoadingEffectTest : SysUiStateTest() {
- private val fakeSystemClock = FakeSystemClock()
- private val fakeExecutor = FakeExecutor(fakeSystemClock)
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
@Test
fun play_paintCallback_triggersDrawCallback() {
@@ -61,14 +62,12 @@ class LoadingEffectTest : SysUiStateTest() {
animationStateChangedCallback = null
)
- fakeExecutor.execute {
- assertThat(paintFromCallback).isNull()
+ assertThat(paintFromCallback).isNull()
- loadingEffect.play()
- fakeSystemClock.advanceTime(500L)
+ loadingEffect.play()
+ animatorTestRule.advanceTimeBy(500L)
- assertThat(paintFromCallback).isNotNull()
- }
+ assertThat(paintFromCallback).isNotNull()
}
@Test
@@ -88,25 +87,22 @@ class LoadingEffectTest : SysUiStateTest() {
animationStateChangedCallback = null
)
- fakeExecutor.execute {
- assertThat(renderEffectFromCallback).isNull()
+ assertThat(renderEffectFromCallback).isNull()
- loadingEffect.play()
- fakeSystemClock.advanceTime(500L)
+ loadingEffect.play()
+ animatorTestRule.advanceTimeBy(500L)
- assertThat(renderEffectFromCallback).isNotNull()
- }
+ assertThat(renderEffectFromCallback).isNotNull()
}
@Test
fun play_animationStateChangesInOrder() {
val config = TurbulenceNoiseAnimationConfig()
- val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
- val actualStates = mutableListOf(NOT_PLAYING)
+ val states = mutableListOf(NOT_PLAYING)
val stateChangedCallback =
object : AnimationStateChangedCallback {
override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
- actualStates.add(newState)
+ states.add(newState)
}
}
val drawCallback =
@@ -121,16 +117,15 @@ class LoadingEffectTest : SysUiStateTest() {
stateChangedCallback
)
- val timeToAdvance =
- config.easeInDuration + config.maxDuration + config.easeOutDuration + 100
+ loadingEffect.play()
- fakeExecutor.execute {
- loadingEffect.play()
+ // Execute all the animators by advancing each duration with some buffer.
+ animatorTestRule.advanceTimeBy(config.easeInDuration.toLong())
+ animatorTestRule.advanceTimeBy(config.maxDuration.toLong())
+ animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
+ animatorTestRule.advanceTimeBy(500)
- fakeSystemClock.advanceTime(timeToAdvance.toLong())
-
- assertThat(actualStates).isEqualTo(expectedStates)
- }
+ assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
}
@Test
@@ -157,17 +152,15 @@ class LoadingEffectTest : SysUiStateTest() {
stateChangedCallback
)
- fakeExecutor.execute {
- assertThat(numPlay).isEqualTo(0)
+ assertThat(numPlay).isEqualTo(0)
- loadingEffect.play()
- loadingEffect.play()
- loadingEffect.play()
- loadingEffect.play()
- loadingEffect.play()
+ loadingEffect.play()
+ loadingEffect.play()
+ loadingEffect.play()
+ loadingEffect.play()
+ loadingEffect.play()
- assertThat(numPlay).isEqualTo(1)
- }
+ assertThat(numPlay).isEqualTo(1)
}
@Test
@@ -181,7 +174,7 @@ class LoadingEffectTest : SysUiStateTest() {
val stateChangedCallback =
object : AnimationStateChangedCallback {
override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
- if (oldState == MAIN && newState == NOT_PLAYING) {
+ if (oldState == EASE_OUT && newState == NOT_PLAYING) {
isFinished = true
}
}
@@ -194,18 +187,17 @@ class LoadingEffectTest : SysUiStateTest() {
stateChangedCallback
)
- fakeExecutor.execute {
- assertThat(isFinished).isFalse()
+ assertThat(isFinished).isFalse()
- loadingEffect.play()
- fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L)
+ loadingEffect.play()
+ animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L)
- assertThat(isFinished).isFalse()
+ assertThat(isFinished).isFalse()
- loadingEffect.finish()
+ loadingEffect.finish()
+ animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L)
- assertThat(isFinished).isTrue()
- }
+ assertThat(isFinished).isTrue()
}
@Test
@@ -232,13 +224,11 @@ class LoadingEffectTest : SysUiStateTest() {
stateChangedCallback
)
- fakeExecutor.execute {
- assertThat(isFinished).isFalse()
+ assertThat(isFinished).isFalse()
- loadingEffect.finish()
+ loadingEffect.finish()
- assertThat(isFinished).isFalse()
- }
+ assertThat(isFinished).isFalse()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
index 549280a809e2..e62ca645d772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_FRACTAL
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_SPARKLE
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,4 +39,9 @@ class TurbulenceNoiseShaderTest : SysuiTestCase() {
fun compilesFractalNoise() {
turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_FRACTAL)
}
+
+ @Test
+ fun compilesSparkleNoise() {
+ turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_SPARKLE)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
new file mode 100644
index 000000000000..875f6ed8d4a8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+
+/** A simple empty vibrator required for the [FakeVibratorHelper] */
+class EmptyVibrator : Vibrator() {
+ override fun cancel() {}
+
+ override fun cancel(usageFilter: Int) {}
+
+ override fun hasAmplitudeControl(): Boolean = true
+
+ override fun hasVibrator(): Boolean = true
+
+ override fun vibrate(
+ uid: Int,
+ opPkg: String,
+ vibe: VibrationEffect,
+ reason: String,
+ attributes: VibrationAttributes,
+ ) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
new file mode 100644
index 000000000000..4c0b132210f1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import android.annotation.SuppressLint
+import android.media.AudioAttributes
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+/** A fake [VibratorHelper] that only keeps track of the latest vibration effects delivered */
+@SuppressLint("VisibleForTests")
+class FakeVibratorHelper : VibratorHelper(EmptyVibrator(), FakeExecutor(FakeSystemClock())) {
+
+ /** A customizable map of primitive ids and their durations in ms */
+ val primitiveDurations: HashMap<Int, Int> = ALL_PRIMITIVE_DURATIONS
+
+ private val vibrationEffectHistory = ArrayList<VibrationEffect>()
+
+ val totalVibrations: Int
+ get() = vibrationEffectHistory.size
+
+ override fun vibrate(effect: VibrationEffect) {
+ vibrationEffectHistory.add(effect)
+ }
+
+ override fun vibrate(effect: VibrationEffect, attributes: VibrationAttributes) = vibrate(effect)
+
+ override fun vibrate(effect: VibrationEffect, attributes: AudioAttributes) = vibrate(effect)
+
+ override fun vibrate(
+ uid: Int,
+ opPkg: String?,
+ vibe: VibrationEffect,
+ reason: String?,
+ attributes: VibrationAttributes,
+ ) = vibrate(vibe)
+
+ override fun getPrimitiveDurations(vararg primitiveIds: Int): IntArray =
+ primitiveIds.map { primitiveDurations[it] ?: 0 }.toIntArray()
+
+ fun hasVibratedWithEffects(vararg effects: VibrationEffect): Boolean =
+ vibrationEffectHistory.containsAll(effects.toList())
+
+ fun timesVibratedWithEffect(effect: VibrationEffect): Int =
+ vibrationEffectHistory.count { it == effect }
+
+ companion object {
+ val ALL_PRIMITIVE_DURATIONS =
+ hashMapOf(
+ VibrationEffect.Composition.PRIMITIVE_NOOP to 0,
+ VibrationEffect.Composition.PRIMITIVE_CLICK to 12,
+ VibrationEffect.Composition.PRIMITIVE_THUD to 300,
+ VibrationEffect.Composition.PRIMITIVE_SPIN to 133,
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE to 150,
+ VibrationEffect.Composition.PRIMITIVE_SLOW_RISE to 500,
+ VibrationEffect.Composition.PRIMITIVE_QUICK_FALL to 100,
+ VibrationEffect.Composition.PRIMITIVE_TICK to 5,
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK to 12,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
new file mode 100644
index 000000000000..434953fb2f43
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.vibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
index dad1887cbd85..f7de5a4c20c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
@@ -23,11 +23,13 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.keyguardTransitionAnimationFlow by Fixture {
KeyguardTransitionAnimationFlow(
scope = applicationCoroutineScope,
+ mainDispatcher = testDispatcher,
transitionInteractor = keyguardTransitionInteractor,
logger = keyguardTransitionAnimationLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..f389142554b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.dreamingToGoneTransitionViewModel by
+ Kosmos.Fixture {
+ DreamingToGoneTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ } \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index a863edfc5198..a84899e0e6ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -46,10 +46,12 @@ val Kosmos.keyguardRootViewModel by Fixture {
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+ goneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel,
lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index f4acf4d8fb53..16c5b72a59e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -31,6 +31,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
@@ -52,6 +53,7 @@ val Kosmos.shadeControllerSceneImpl by
notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
deviceEntryInteractor = deviceEntryInteractor,
touchLog = mock<LogBuffer>(),
+ vibratorHelper = mock<VibratorHelper>(),
commandQueue = mock<CommandQueue>(),
statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
notificationShadeWindowController = mock<NotificationShadeWindowController>(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index f1767ebb10d1..930a4bbb2daa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
@@ -42,5 +43,6 @@ val Kosmos.notificationListViewModel by Fixture {
shadeInteractor,
userSetupInteractor,
zenModeInteractor,
+ testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt
new file mode 100644
index 000000000000..5db17243c4e3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
+import android.media.AudioAttributes
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+private const val LOCAL_PACKAGE = "local.test.pkg"
+var Kosmos.localMediaController: MediaController by
+ Kosmos.Fixture {
+ val appInfo: ApplicationInfo = mock {
+ whenever(loadLabel(any())).thenReturn("local_media_controller_label")
+ }
+ whenever(packageManager.getApplicationInfo(eq(LOCAL_PACKAGE), any<Int>()))
+ .thenReturn(appInfo)
+
+ val localSessionToken: MediaSession.Token = MediaSession.Token(0, mock {})
+ mock {
+ whenever(packageName).thenReturn(LOCAL_PACKAGE)
+ whenever(playbackInfo)
+ .thenReturn(
+ MediaController.PlaybackInfo(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+ 0,
+ 0,
+ 0,
+ AudioAttributes.Builder().build(),
+ "",
+ )
+ )
+ whenever(sessionToken).thenReturn(localSessionToken)
+ }
+ }
+
+private const val REMOTE_PACKAGE = "remote.test.pkg"
+var Kosmos.remoteMediaController: MediaController by
+ Kosmos.Fixture {
+ val appInfo: ApplicationInfo = mock {
+ whenever(loadLabel(any())).thenReturn("remote_media_controller_label")
+ }
+ whenever(packageManager.getApplicationInfo(eq(REMOTE_PACKAGE), any<Int>()))
+ .thenReturn(appInfo)
+
+ val remoteSessionToken: MediaSession.Token = MediaSession.Token(0, mock {})
+ mock {
+ whenever(packageName).thenReturn(REMOTE_PACKAGE)
+ whenever(playbackInfo)
+ .thenReturn(
+ MediaController.PlaybackInfo(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+ 0,
+ 0,
+ 0,
+ AudioAttributes.Builder().build(),
+ "",
+ )
+ )
+ whenever(sessionToken).thenReturn(remoteSessionToken)
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3938f77b9c54..fa3a19bae655 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -18,7 +18,6 @@ package com.android.systemui.volume
import android.content.packageManager
import android.content.pm.ApplicationInfo
-import android.media.session.MediaController
import android.os.Handler
import android.testing.TestableLooper
import com.android.systemui.kosmos.Kosmos
@@ -32,11 +31,10 @@ import com.android.systemui.volume.data.repository.FakeLocalMediaRepository
import com.android.systemui.volume.data.repository.FakeMediaControllerRepository
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.FakeLocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-var Kosmos.mediaController: MediaController by Kosmos.Fixture { mock {} }
-
val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() }
val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
@@ -56,6 +54,14 @@ val Kosmos.mediaOutputInteractor by
},
testScope.backgroundScope,
testScope.testScheduler,
+ mediaControllerRepository,
+ )
+ }
+
+val Kosmos.mediaDeviceSessionInteractor by
+ Kosmos.Fixture {
+ MediaDeviceSessionInteractor(
+ testScope.testScheduler,
Handler(TestableLooper.get(testCase).looper),
mediaControllerRepository,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
index 284bd55f15d7..909be7507d34 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
@@ -17,7 +17,6 @@
package com.android.systemui.volume.data.repository
import com.android.settingslib.media.MediaDevice
-import com.android.settingslib.volume.data.model.RoutingSession
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -25,35 +24,11 @@ import kotlinx.coroutines.flow.asStateFlow
class FakeLocalMediaRepository : LocalMediaRepository {
- private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
-
- private val mutableMediaDevices = MutableStateFlow<List<MediaDevice>>(emptyList())
- override val mediaDevices: StateFlow<List<MediaDevice>>
- get() = mutableMediaDevices.asStateFlow()
-
private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
override val currentConnectedDevice: StateFlow<MediaDevice?>
get() = mutableCurrentConnectedDevice.asStateFlow()
- private val mutableRemoteRoutingSessions = MutableStateFlow<List<RoutingSession>>(emptyList())
- override val remoteRoutingSessions: StateFlow<List<RoutingSession>>
- get() = mutableRemoteRoutingSessions.asStateFlow()
-
- fun updateMediaDevices(devices: List<MediaDevice>) {
- mutableMediaDevices.value = devices
- }
-
fun updateCurrentConnectedDevice(device: MediaDevice?) {
mutableCurrentConnectedDevice.value = device
}
-
- fun updateRemoteRoutingSessions(sessions: List<RoutingSession>) {
- mutableRemoteRoutingSessions.value = sessions
- }
-
- fun getSessionVolume(sessionId: String?): Int = volumeBySession.getOrDefault(sessionId, 0)
-
- override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
- volumeBySession[sessionId] = volume
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt
index 6d52e525d238..8ab5bd903fdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt
@@ -24,11 +24,11 @@ import kotlinx.coroutines.flow.asStateFlow
class FakeMediaControllerRepository : MediaControllerRepository {
- private val mutableActiveLocalMediaController = MutableStateFlow<MediaController?>(null)
- override val activeLocalMediaController: StateFlow<MediaController?> =
- mutableActiveLocalMediaController.asStateFlow()
+ private val mutableActiveSessions = MutableStateFlow<List<MediaController>>(emptyList())
+ override val activeSessions: StateFlow<List<MediaController>>
+ get() = mutableActiveSessions.asStateFlow()
- fun setActiveLocalMediaController(controller: MediaController?) {
- mutableActiveLocalMediaController.value = controller
+ fun setActiveSessions(sessions: List<MediaController>) {
+ mutableActiveSessions.value = sessions
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 98a7979de30a..7bbb42e9a88f 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -253,6 +253,7 @@ java_library {
required: [
"libukey2_jni_shared",
+ "protolog.conf.json.gz",
],
lint: {
baseline_filename: "lint-baseline.xml",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4e14dee8acba..880a68776055 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -993,6 +993,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
}
+ } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+ restoreAccessibilityQsTargets(
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
}
}
}
@@ -2131,6 +2137,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
onUserStateChangedLocked(userState);
}
+ /**
+ * User could configure accessibility shortcut during the SUW before restoring user data.
+ * Merges the current value and the new value to make sure we don't lost the setting the user's
+ * preferences of accessibility qs shortcut updated in SUW are not lost.
+ *
+ * Called only during settings restore; currently supports only the owner user
+ * TODO: http://b/22388012
+ */
+ private void restoreAccessibilityQsTargets(String newValue) {
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+ final Set<String> mergedTargets = userState.getA11yQsTargets();
+ readColonDelimitedStringToSet(newValue, str -> str, mergedTargets,
+ /* doMerge = */ true);
+
+ userState.updateA11yQsTargetLocked(mergedTargets);
+ persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ UserHandle.USER_SYSTEM, mergedTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ onUserStateChangedLocked(userState);
+ }
+ }
+
private int getClientStateLocked(AccessibilityUserState userState) {
return userState.getClientStateLocked(
mUiAutomationManager.canIntrospect(),
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 9a1d3793e447..7008e8e0f0ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -112,6 +112,10 @@ class AccessibilityUserState {
* TileService's or the a11y framework tile component names (e.g.
* {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
* A11y Feature's component names.
+ * <p/>
+ * In addition, {@link #mA11yTilesInQsPanel} stores what's on the QS Panel, whereas
+ * {@link #mAccessibilityQsTargets} stores the targets that configured qs as their shortcut and
+ * also grant full device control permission.
*/
private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 08093c0c037f..e64a87f3966b 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -45,7 +45,6 @@ import android.util.SparseArray;
import com.android.internal.pm.pkg.component.ParsedMainComponent;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.KnownPackages;
import com.android.server.pm.PackageArchiver;
import com.android.server.pm.PackageList;
@@ -1396,21 +1395,6 @@ public abstract class PackageManagerInternal {
@UserIdInt int userId, @Nullable String recentCallingPackage,
@NonNull String debugInfo);
- /** @deprecated For legacy shell command only. */
- @Deprecated
- public abstract void legacyDumpProfiles(@NonNull String packageName,
- boolean dumpClassesAndMethods) throws LegacyDexoptDisabledException;
-
- /** @deprecated For legacy shell command only. */
- @Deprecated
- public abstract void legacyForceDexOpt(@NonNull String packageName)
- throws LegacyDexoptDisabledException;
-
- /** @deprecated For legacy shell command only. */
- @Deprecated
- public abstract void legacyReconcileSecondaryDexFiles(String packageName)
- throws LegacyDexoptDisabledException;
-
/**
* Gets {@link PackageManager.DistractionRestriction restrictions} of the given
* packages of the given user.
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index ecd14ce67d7e..cc4094092572 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -77,23 +77,24 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
@Override
public void onStart(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStart projection: " + info);
- Trace.beginSection(
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
onProjectionStart(info.getPackageName());
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
- Trace.beginSection("SensitiveContentProtectionManagerService.onProjectionStop");
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "SensitiveContentProtectionManagerService.onProjectionStop");
try {
onProjectionEnd();
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
};
@@ -285,7 +286,8 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
@Override
public void onListenerConnected() {
super.onListenerConnected();
- Trace.beginSection("SensitiveContentProtectionManagerService.onListenerConnected");
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "SensitiveContentProtectionManagerService.onListenerConnected");
try {
// Projection started before notification listener was connected
synchronized (mSensitiveContentProtectionLock) {
@@ -294,14 +296,15 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
super.onNotificationPosted(sbn, rankingMap);
- Trace.beginSection("SensitiveContentProtectionManagerService.onNotificationPosted");
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+ "SensitiveContentProtectionManagerService.onNotificationPosted");
try {
synchronized (mSensitiveContentProtectionLock) {
if (!mProjectionActive) {
@@ -317,14 +320,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
@Override
public void onNotificationRankingUpdate(RankingMap rankingMap) {
super.onNotificationRankingUpdate(rankingMap);
- Trace.beginSection(
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onNotificationRankingUpdate");
try {
synchronized (mSensitiveContentProtectionLock) {
@@ -333,7 +336,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
}
@@ -382,7 +385,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
public void setSensitiveContentProtection(IBinder windowToken, String packageName,
boolean isShowingSensitiveContent) {
- Trace.beginSection(
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.setSensitiveContentProtection");
try {
int callingUid = Binder.getCallingUid();
@@ -395,7 +398,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
Binder.restoreCallingIdentity(identity);
}
} finally {
- Trace.endSection();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7df5fdd282c3..48d3c09290ce 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -127,6 +127,7 @@ public class SettingsToPropertiesMapper {
"avic",
"bluetooth",
"brownout_mitigation_audio",
+ "brownout_mitigation_modem",
"build",
"biometrics",
"biometrics_framework",
@@ -168,6 +169,7 @@ public class SettingsToPropertiesMapper {
"pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
+ "pixel_sensors",
"pixel_system_sw_video",
"pixel_watch",
"platform_compat",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 70d447fce18a..a91790936931 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -276,7 +276,15 @@ class UserController implements Handler.Callback {
private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
/**
- * LRU list of history of current users. Most recently current is at the end.
+ * LRU list of history of running users, in order of when we last needed to start them.
+ *
+ * Switching to a user will move it towards the end. Attempting to start a user/profile (even
+ * if it was already running) will move it towards the end.
+ *
+ * <p>Guarantees (by the end of startUser):
+ * <li>The current user will always be at the end, even if background users were started
+ * subsequently.
+ * <li>Parents always come later than (but not necessarily adjacent to) their profiles.
*/
@GuardedBy("mLock")
private final ArrayList<Integer> mUserLru = new ArrayList<>();
@@ -299,6 +307,9 @@ class UserController implements Handler.Callback {
/**
* Mapping from each known user ID to the profile group ID it is associated with.
* <p>Users not present in this array have a profile group of NO_PROFILE_GROUP_ID.
+ *
+ * <p>For better or worse, this class sometimes assumes that the profileGroupId of a parent user
+ * is always identical with its userId. If that ever becomes false, this class needs updating.
*/
@GuardedBy("mLock")
private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
@@ -499,6 +510,23 @@ class UserController implements Handler.Callback {
});
}
+ /** Adds a user to mUserLru, moving it to the end of the list if it was already present. */
+ private void addUserToUserLru(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final Integer userIdObj = userId;
+ mUserLru.remove(userIdObj);
+ mUserLru.add(userIdObj);
+
+ // Now also move the user's parent to the end (if applicable).
+ Integer parentIdObj = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentIdObj != UserInfo.NO_PROFILE_GROUP_ID && !parentIdObj.equals(userIdObj)
+ && mUserLru.remove(parentIdObj)) {
+ mUserLru.add(parentIdObj);
+ }
+ }
+ }
+
+ /** Returns a list of running users, in order of when they were started (oldest first). */
@GuardedBy("mLock")
@VisibleForTesting
List<Integer> getRunningUsersLU() {
@@ -536,9 +564,9 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock")
private void stopExcessRunningUsersLU(int maxRunningUsers, ArraySet<Integer> exemptedUsers) {
- List<Integer> currentlyRunning = getRunningUsersLU();
- Iterator<Integer> iterator = currentlyRunning.iterator();
- while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
+ List<Integer> currentlyRunningLru = getRunningUsersLU();
+ Iterator<Integer> iterator = currentlyRunningLru.iterator();
+ while (currentlyRunningLru.size() > maxRunningUsers && iterator.hasNext()) {
Integer userId = iterator.next();
if (userId == UserHandle.USER_SYSTEM
|| userId == mCurrentUserId
@@ -551,6 +579,10 @@ class UserController implements Handler.Callback {
if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
+ // Technically, stopUsersLU can remove more than one user when stopping a parent.
+ // But mUserLru is designed so that profiles always precede their parent, so this
+ // normally won't happen here, and therefore won't cause underestimating the number
+ // removed.
iterator.remove();
}
}
@@ -947,7 +979,7 @@ class UserController implements Handler.Callback {
}
/**
- * Stops the user along with its related users. The method calls
+ * Stops the user along with its profiles. The method calls
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
*/
@GuardedBy("mLock")
@@ -992,7 +1024,12 @@ class UserController implements Handler.Callback {
}
/**
- * Stops a single User. This can also trigger locking user data out depending on device's
+ * Stops a single User.
+ *
+ * This should only ever be called by {@link #stopUsersLU},
+ * which is responsible to making sure any associated users are appropriately stopped too.
+ *
+ * This can also trigger locking user data out depending on device's
* config ({@code mDelayUserDataLocking}) and arguments.
*
* In the default configuration for most device and users, users will be locked when stopping.
@@ -1425,7 +1462,8 @@ class UserController implements Handler.Callback {
/**
* Determines the list of users that should be stopped together with the specified
- * {@code userId}. The returned list includes {@code userId}.
+ * {@code userId}, i.e. the user and its profiles (if the given user is a parent).
+ * The returned list includes {@code userId}.
*/
@GuardedBy("mLock")
private @NonNull int[] getUsersToStopLU(@UserIdInt int userId) {
@@ -1433,20 +1471,23 @@ class UserController implements Handler.Callback {
IntArray userIds = new IntArray();
userIds.add(userId);
int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
- for (int i = 0; i < startedUsersSize; i++) {
- UserState uss = mStartedUsers.valueAt(i);
- int startedUserId = uss.mHandle.getIdentifier();
- // Skip unrelated users (profileGroupId mismatch)
- int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
- UserInfo.NO_PROFILE_GROUP_ID);
- boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
- && (userGroupId == startedUserGroupId);
- // userId has already been added
- boolean sameUserId = startedUserId == userId;
- if (!sameGroup || sameUserId) {
- continue;
+ if (userGroupId == userId) {
+ // The user is the parent of the profile group. Stop its profiles too.
+ for (int i = 0; i < startedUsersSize; i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ int startedUserId = uss.mHandle.getIdentifier();
+ // Skip unrelated users (profileGroupId mismatch)
+ int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+ && (userGroupId == startedUserGroupId);
+ // userId has already been added
+ boolean sameUserId = startedUserId == userId;
+ if (!sameGroup || sameUserId) {
+ continue;
+ }
+ userIds.add(startedUserId);
}
- userIds.add(startedUserId);
}
return userIds.toArray();
}
@@ -1519,6 +1560,7 @@ class UserController implements Handler.Callback {
});
}
+ /** Starts all applicable profiles of the current user. */
private void startProfiles() {
int currentUserId = getCurrentUserId();
if (DEBUG_MU) Slogf.i(TAG, "startProfilesLocked");
@@ -1711,6 +1753,7 @@ class UserController implements Handler.Callback {
t.traceBegin("getStartedUserState");
final int oldUserId = getCurrentUserId();
if (oldUserId == userId) {
+ // The user we're requested to start is already the current user.
final UserState state = getStartedUserState(userId);
if (state == null) {
Slogf.wtf(TAG, "Current user has no UserState");
@@ -1793,10 +1836,12 @@ class UserController implements Handler.Callback {
t.traceEnd(); // updateStartedUserArrayStarting
return true;
}
- final Integer userIdInt = userId;
- mUserLru.remove(userIdInt);
- mUserLru.add(userIdInt);
}
+
+ // No matter what, the fact that we're requested to start the user (even if it is
+ // already running) puts it towards the end of the mUserLru list.
+ addUserToUserLru(userId);
+
if (unlockListener != null) {
uss.mUnlockProgress.addListener(unlockListener);
}
@@ -1835,12 +1880,10 @@ class UserController implements Handler.Callback {
}
} else {
- final Integer currentUserIdInt = mCurrentUserId;
updateProfileRelatedCaches();
- synchronized (mLock) {
- mUserLru.remove(currentUserIdInt);
- mUserLru.add(currentUserIdInt);
- }
+ // We are starting a non-foreground user. They have already been added to the end
+ // of mUserLru, so we need to ensure that the foreground user isn't displaced.
+ addUserToUserLru(mCurrentUserId);
}
t.traceEnd();
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index d93ff9dac91f..086f3aa8ad65 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -138,7 +138,9 @@ public final class BroadcastRadioServiceImpl {
/**
* Constructs BroadcastRadioServiceImpl using AIDL HAL using the list of names of AIDL
- * BroadcastRadio HAL services {@code serviceNameList}
+ * BroadcastRadio HAL services
+ *
+ * @param serviceNameList list of names of AIDL BroadcastRadio HAL services
*/
public BroadcastRadioServiceImpl(ArrayList<String> serviceNameList) {
mNextModuleId = 0;
@@ -169,7 +171,11 @@ public final class BroadcastRadioServiceImpl {
}
/**
- * Gets the AIDL RadioModule for the given {@code moduleId}. Null will be returned if not found.
+ * Gets the AIDL RadioModule for the given module Id.
+ *
+ * @param id Id of {@link RadioModule} of AIDL BroadcastRadio HAL service
+ * @return {@code true} if {@link RadioModule} of AIDL BroadcastRadio HAL service is found,
+ * {@code false} otherwise
*/
public boolean hasModule(int id) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 40325146ca25..4aab9d26dbcb 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -54,6 +54,7 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -252,6 +253,7 @@ public class AutomaticBrightnessController {
// Controls Brightness range (including High Brightness Mode).
private final BrightnessRangeController mBrightnessRangeController;
+ private final BrightnessClamperController mBrightnessClamperController;
// Throttles (caps) maximum allowed brightness
private final BrightnessThrottler mBrightnessThrottler;
@@ -287,7 +289,8 @@ public class AutomaticBrightnessController {
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
+ int ambientLightHorizonLong, float userLux, float userNits,
+ BrightnessClamperController brightnessClamperController) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor,
brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin, brightnessMax,
dozeScaleFactor, lightSensorRate, initialLightSensorRate,
@@ -297,7 +300,7 @@ public class AutomaticBrightnessController {
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits
+ userNits, brightnessClamperController
);
}
@@ -313,9 +316,10 @@ public class AutomaticBrightnessController {
HysteresisLevels screenBrightnessThresholds,
HysteresisLevels ambientBrightnessThresholdsIdle,
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
- BrightnessRangeController brightnessModeController,
+ BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
+ int ambientLightHorizonLong, float userLux, float userNits,
+ BrightnessClamperController brightnessClamperController) {
mInjector = injector;
mClock = injector.createClock();
mContext = context;
@@ -358,7 +362,8 @@ public class AutomaticBrightnessController {
mPendingForegroundAppPackageName = null;
mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
- mBrightnessRangeController = brightnessModeController;
+ mBrightnessRangeController = brightnessRangeController;
+ mBrightnessClamperController = brightnessClamperController;
mBrightnessThrottler = brightnessThrottler;
mBrightnessMappingStrategyMap = brightnessMappingStrategyMap;
@@ -791,7 +796,7 @@ public class AutomaticBrightnessController {
mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
}
mBrightnessRangeController.onAmbientLuxChange(mAmbientLux);
-
+ mBrightnessClamperController.onAmbientLuxChange(mAmbientLux);
// If the short term model was invalidated and the change is drastic enough, reset it.
mShortTermModel.maybeReset(mAmbientLux);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 411666942b6d..04e7f77615a6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -61,6 +61,7 @@ import com.android.server.display.config.IdleScreenRefreshRateTimeout;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
import com.android.server.display.config.IntegerArray;
+import com.android.server.display.config.LowBrightnessData;
import com.android.server.display.config.LuxThrottling;
import com.android.server.display.config.NitsMap;
import com.android.server.display.config.NonNegativeFloatToFloatPoint;
@@ -555,6 +556,24 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <majorVersion>2</majorVersion>
* <minorVersion>0</minorVersion>
* </usiVersion>
+ * <lowBrightness enabled="true">
+ * <transitionPoint>0.1</transitionPoint>
+ *
+ * <nits>0.2</nits>
+ * <nits>2.0</nits>
+ * <nits>500.0</nits>
+ * <nits>1000.0</nits>
+ *
+ * <backlight>0</backlight>
+ * <backlight>0.0001</backlight>
+ * <backlight>0.5</backlight>
+ * <backlight>1.0</backlight>
+ *
+ * <brightness>0</brightness>
+ * <brightness>0.1</brightness>
+ * <brightness>0.5</brightness>
+ * <brightness>1.0</brightness>
+ * </lowBrightness>
* <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode>
* <idleScreenRefreshRateTimeout>
* <luxThresholds>
@@ -568,6 +587,8 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </luxThresholds>
* </idleScreenRefreshRateTimeout>
+ *
+ *
* </displayConfiguration>
* }
* </pre>
@@ -732,6 +753,7 @@ public class DisplayDeviceConfig {
private Spline mBacklightToBrightnessSpline;
private Spline mBacklightToNitsSpline;
private Spline mNitsToBacklightSpline;
+
private List<String> mQuirks;
private boolean mIsHighBrightnessModeEnabled = false;
private HighBrightnessModeData mHbmData;
@@ -872,6 +894,9 @@ public class DisplayDeviceConfig {
@Nullable
private HdrBrightnessData mHdrBrightnessData;
+ @Nullable
+ public LowBrightnessData mLowBrightnessData;
+
/**
* Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
*/
@@ -1814,6 +1839,15 @@ public class DisplayDeviceConfig {
}
/**
+ *
+ * @return true if low brightness mode is enabled
+ */
+ @VisibleForTesting
+ public boolean getLbmEnabled() {
+ return mLowBrightnessData != null;
+ }
+
+ /**
* @return Maximum screen brightness setting when screen brightness capped in Wear Bedtime mode.
*/
public float getBrightnessCapForWearBedtimeMode() {
@@ -1952,6 +1986,8 @@ public class DisplayDeviceConfig {
+ "mUsiVersion= " + mHostUsiVersion + "\n"
+ "mHdrBrightnessData= " + mHdrBrightnessData + "\n"
+ "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode
+ + "\n"
+ + (mLowBrightnessData != null ? mLowBrightnessData.toString() : "")
+ "}";
}
@@ -2002,6 +2038,9 @@ public class DisplayDeviceConfig {
loadDensityMapping(config);
loadBrightnessDefaultFromDdcXml(config);
loadBrightnessConstraintsFromConfigXml();
+ if (mFlags.isEvenDimmerEnabled()) {
+ mLowBrightnessData = LowBrightnessData.loadConfig(config);
+ }
loadBrightnessMap(config);
loadThermalThrottlingConfig(config);
loadPowerThrottlingConfigData(config);
@@ -2793,6 +2832,18 @@ public class DisplayDeviceConfig {
// These splines are used to convert from the system brightness value to the HAL backlight
// value
private void createBacklightConversionSplines() {
+ if (mLowBrightnessData != null) {
+ mBrightnessToBacklightSpline = mLowBrightnessData.mBrightnessToBacklight;
+ mBacklightToBrightnessSpline = mLowBrightnessData.mBacklightToBrightness;
+ mBacklightToNitsSpline = mLowBrightnessData.mBacklightToNits;
+ mNitsToBacklightSpline = mLowBrightnessData.mNitsToBacklight;
+
+ mNits = mLowBrightnessData.mNits;
+ mBrightness = mLowBrightnessData.mBrightness;
+ mBacklight = mLowBrightnessData.mBacklight;
+ return;
+ }
+
mBrightness = new float[mBacklight.length];
for (int i = 0; i < mBrightness.length; i++) {
mBrightness[i] = MathUtils.map(mBacklight[0],
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 87d017c978b1..90ad8c02c29c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1165,7 +1165,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController,
mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(),
- mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits);
+ mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits,
+ mBrightnessClamperController);
mDisplayBrightnessController.setAutomaticBrightnessController(
mAutomaticBrightnessController);
@@ -2479,6 +2480,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
boolean slowChange) {
mBrightnessRangeController.onAmbientLuxChange(ambientLux);
+ mBrightnessClamperController.onAmbientLuxChange(ambientLux);
if (nits == BrightnessMappingStrategy.INVALID_NITS) {
mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
} else {
@@ -3176,7 +3178,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessModeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
+ int ambientLightHorizonLong, float userLux, float userNits,
+ BrightnessClamperController brightnessClamperController) {
+
return new AutomaticBrightnessController(callbacks, looper, sensorManager, lightSensor,
brightnessMappingStrategyMap, lightSensorWarmUpTime, brightnessMin,
brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate,
@@ -3186,7 +3190,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
screenBrightnessThresholds, ambientBrightnessThresholdsIdle,
screenBrightnessThresholdsIdle, context, brightnessModeController,
brightnessThrottler, ambientLightHorizonShort, ambientLightHorizonLong, userLux,
- userNits);
+ userNits, brightnessClamperController);
}
BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b2fd9edf61fe..3b3a03bce524 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -37,6 +37,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.util.DisplayUtils;
import android.util.LongSparseArray;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -78,6 +79,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final String UNIQUE_ID_PREFIX = "local:";
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
+ // Min and max strengths for even dimmer feature.
+ private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
+ private static final float EVEN_DIMMER_MAX_STRENGTH = 70.0f; // not too dim yet.
+ private static final float BRIGHTNESS_MIN = 0.0f;
+ // The brightness at which we start using color matrices rather than backlight,
+ // to dim the display
+ private static final float BACKLIGHT_COLOR_TRANSITION_POINT = 0.1f;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -91,6 +99,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private Context mOverlayContext;
+ private int mEvenDimmerStrength = -1;
+
// Called with SyncRoot lock held.
LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
Handler handler, Listener listener, DisplayManagerFlags flags,
@@ -928,6 +938,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
final float nits = backlightToNits(backlight);
final float sdrNits = backlightToNits(sdrBacklight);
+ if (getFeatureFlags().isEvenDimmerEnabled()) {
+ applyColorMatrixBasedDimming(brightnessState);
+ }
+
mBacklightAdapter.setBacklight(sdrBacklight, sdrNits, backlight, nits);
Trace.traceCounter(Trace.TRACE_TAG_POWER,
"ScreenBrightness",
@@ -974,6 +988,22 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
}
+
+ private void applyColorMatrixBasedDimming(float brightnessState) {
+ int strength = (int) (MathUtils.constrainedMap(
+ EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH, // to this range
+ BRIGHTNESS_MIN, BACKLIGHT_COLOR_TRANSITION_POINT, // from this range
+ brightnessState) + 0.5); // map this (+ rounded up)
+
+ if (mEvenDimmerStrength < 0 // uninitialised
+ || MathUtils.abs(mEvenDimmerStrength - strength) > 1
+ || strength <= 1) {
+ mEvenDimmerStrength = strength;
+ }
+
+ // TODO: use `enabled` and `mRbcStrength` to set color matrices here
+ // TODO: boolean enabled = mEvenDimmerStrength > 0.0f;
+ }
};
}
return null;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 18e8fab54e3e..d8a45009f236 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -189,6 +189,13 @@ public class BrightnessClamperController {
mModifiers.forEach(BrightnessStateModifier::stop);
}
+ /**
+ * Notifies modifiers that ambient lux has changed.
+ * @param ambientLux current lux, debounced
+ */
+ public void onAmbientLuxChange(float ambientLux) {
+ mModifiers.forEach(modifier -> modifier.onAmbientLuxChange(ambientLux));
+ }
// Called in DisplayControllerHandler
private void recalculateBrightnessCap() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index 7f1f7a99e438..a91bb59b0bc0 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -39,21 +39,21 @@ import java.io.PrintWriter;
* Class used to prevent the screen brightness dipping below a certain value, based on current
* lux conditions and user preferred minimum.
*/
-public class BrightnessLowLuxModifier implements
- BrightnessStateModifier {
+public class BrightnessLowLuxModifier extends BrightnessModifier {
// To enable these logs, run:
// 'adb shell setprop persist.log.tag.BrightnessLowLuxModifier DEBUG && adb reboot'
private static final String TAG = "BrightnessLowLuxModifier";
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private static final float MIN_NITS = 2.0f;
private final SettingsObserver mSettingsObserver;
private final ContentResolver mContentResolver;
private final Handler mHandler;
private final BrightnessClamperController.ClamperChangeListener mChangeListener;
- protected float mSettingNitsLowerBound = PowerManager.BRIGHTNESS_MIN;
private int mReason;
private float mBrightnessLowerBound;
private boolean mIsActive;
+ private float mAmbientLux;
@VisibleForTesting
BrightnessLowLuxModifier(Handler handler,
@@ -78,17 +78,17 @@ public class BrightnessLowLuxModifier implements
int userId = UserHandle.USER_CURRENT;
float settingNitsLowerBound = Settings.Secure.getFloatForUser(
mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ PowerManager.BRIGHTNESS_MIN, userId);
+ /* def= */ MIN_NITS, userId);
- boolean isActive = Settings.Secure.getIntForUser(mContentResolver,
+ boolean isActive = Settings.Secure.getFloatForUser(mContentResolver,
Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 0, userId) == 1;
+ /* def= */ 0, userId) == 1.0f;
- // TODO: luxBasedNitsLowerBound = mMinNitsToLuxSpline(currentLux);
- float luxBasedNitsLowerBound = 0.0f;
+ // TODO: luxBasedNitsLowerBound = mMinLuxToNitsSpline(currentLux);
+ float luxBasedNitsLowerBound = 2.0f;
- // TODO: final float nitsLowerBound = isActive ? Math.max(settingNitsLowerBound,
- // luxBasedNitsLowerBound) : PowerManager.BRIGHTNESS_MIN;
+ final float nitsLowerBound = isActive ? Math.max(settingNitsLowerBound,
+ luxBasedNitsLowerBound) : MIN_NITS;
final int reason = settingNitsLowerBound > luxBasedNitsLowerBound
? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
@@ -104,8 +104,13 @@ public class BrightnessLowLuxModifier implements
mReason = reason;
if (DEBUG) {
Slog.i(TAG, "isActive: " + isActive
- + ", settingNitsLowerBound: " + settingNitsLowerBound
- + ", lowerBound: " + brightnessLowerBound);
+ + ", brightnessLowerBound: " + brightnessLowerBound
+ + ", mAmbientLux: " + mAmbientLux
+ + ", mReason: " + (
+ mReason == BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND ? "minSetting"
+ : "lux")
+ + ", nitsLowerBound: " + nitsLowerBound
+ );
}
mBrightnessLowerBound = brightnessLowerBound;
mChangeListener.onChanged();
@@ -132,6 +137,22 @@ public class BrightnessLowLuxModifier implements
}
@Override
+ boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request) {
+ return mIsActive;
+ }
+
+ @Override
+ float getBrightnessAdjusted(float currentBrightness,
+ DisplayManagerInternal.DisplayPowerRequest request) {
+ return Math.max(mBrightnessLowerBound, currentBrightness);
+ }
+
+ @Override
+ int getModifier() {
+ return mReason;
+ }
+
+ @Override
public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
stateBuilder.setMinBrightness(mBrightnessLowerBound);
@@ -150,10 +171,16 @@ public class BrightnessLowLuxModifier implements
}
@Override
+ public void onAmbientLuxChange(float ambientLux) {
+ mAmbientLux = ambientLux;
+ recalculateLowerBound();
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("BrightnessLowLuxModifier:");
- pw.println(" mBrightnessLowerBound=" + mBrightnessLowerBound);
pw.println(" mIsActive=" + mIsActive);
+ pw.println(" mBrightnessLowerBound=" + mBrightnessLowerBound);
pw.println(" mReason=" + mReason);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index be8fa5a0f0ce..2a3dd8752615 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -68,4 +68,9 @@ abstract class BrightnessModifier implements BrightnessStateModifier {
public void stop() {
// do nothing
}
+
+ @Override
+ public void onAmbientLuxChange(float ambientLux) {
+ // do nothing
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
index 441ba8f1a1fc..22342581fa8b 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
@@ -42,4 +42,10 @@ public interface BrightnessStateModifier {
* Called when stopped. Listeners can be unregistered here.
*/
void stop();
+
+ /**
+ * Allows modifiers to react to ambient lux changes.
+ * @param ambientLux current debounced lux.
+ */
+ void onAmbientLuxChange(float ambientLux);
}
diff --git a/services/core/java/com/android/server/display/config/LowBrightnessData.java b/services/core/java/com/android/server/display/config/LowBrightnessData.java
new file mode 100644
index 000000000000..aa82533bf6a7
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/LowBrightnessData.java
@@ -0,0 +1,142 @@
+/*
+ * 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.server.display.config;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.Spline;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Brightness config for low brightness mode
+ */
+public class LowBrightnessData {
+ private static final String TAG = "LowBrightnessData";
+
+ /**
+ * Brightness value at which lower brightness methods are used.
+ */
+ public final float mTransitionPoint;
+
+ /**
+ * Nits array, maps to mBacklight
+ */
+ public final float[] mNits;
+
+ /**
+ * Backlight array, maps to mBrightness and mNits
+ */
+ public final float[] mBacklight;
+
+ /**
+ * Brightness array, maps to mBacklight
+ */
+ public final float[] mBrightness;
+ /**
+ * Spline, mapping between backlight and nits
+ */
+ public final Spline mBacklightToNits;
+ /**
+ * Spline, mapping between nits and backlight
+ */
+ public final Spline mNitsToBacklight;
+ /**
+ * Spline, mapping between brightness and backlight
+ */
+ public final Spline mBrightnessToBacklight;
+ /**
+ * Spline, mapping between backlight and brightness
+ */
+ public final Spline mBacklightToBrightness;
+
+ @VisibleForTesting
+ public LowBrightnessData(float transitionPoint, float[] nits,
+ float[] backlight, float[] brightness, Spline backlightToNits,
+ Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness) {
+ mTransitionPoint = transitionPoint;
+ mNits = nits;
+ mBacklight = backlight;
+ mBrightness = brightness;
+ mBacklightToNits = backlightToNits;
+ mNitsToBacklight = nitsToBacklight;
+ mBrightnessToBacklight = brightnessToBacklight;
+ mBacklightToBrightness = backlightToBrightness;
+ }
+
+ @Override
+ public String toString() {
+ return "LowBrightnessData {"
+ + "mTransitionPoint: " + mTransitionPoint
+ + ", mNits: " + Arrays.toString(mNits)
+ + ", mBacklight: " + Arrays.toString(mBacklight)
+ + ", mBrightness: " + Arrays.toString(mBrightness)
+ + ", mBacklightToNits: " + mBacklightToNits
+ + ", mNitsToBacklight: " + mNitsToBacklight
+ + ", mBrightnessToBacklight: " + mBrightnessToBacklight
+ + ", mBacklightToBrightness: " + mBacklightToBrightness
+ + "} ";
+ }
+
+ /**
+ * Loads LowBrightnessData from DisplayConfiguration
+ */
+ @Nullable
+ public static LowBrightnessData loadConfig(DisplayConfiguration config) {
+ final LowBrightnessMode lbm = config.getLowBrightness();
+ if (lbm == null) {
+ return null;
+ }
+
+ boolean lbmIsEnabled = lbm.getEnabled();
+ if (!lbmIsEnabled) {
+ return null;
+ }
+
+ List<Float> nitsList = lbm.getNits();
+ List<Float> backlightList = lbm.getBacklight();
+ List<Float> brightnessList = lbm.getBrightness();
+ float transitionPoints = lbm.getTransitionPoint().floatValue();
+
+ if (nitsList.isEmpty()
+ || backlightList.size() != brightnessList.size()
+ || backlightList.size() != nitsList.size()) {
+ Slog.e(TAG, "Invalid low brightness array lengths");
+ return null;
+ }
+
+ float[] nits = new float[nitsList.size()];
+ float[] backlight = new float[nitsList.size()];
+ float[] brightness = new float[nitsList.size()];
+
+ for (int i = 0; i < nitsList.size(); i++) {
+ nits[i] = nitsList.get(i);
+ backlight[i] = backlightList.get(i);
+ brightness[i] = brightnessList.get(i);
+ }
+
+ return new LowBrightnessData(transitionPoints, nits, backlight, brightness,
+ Spline.createSpline(backlight, nits),
+ Spline.createSpline(nits, backlight),
+ Spline.createSpline(brightness, backlight),
+ Spline.createSpline(backlight, brightness)
+ );
+ }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 283e692ffbab..661008103a25 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -459,13 +459,16 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
for (ResolveInfo resolveInfo : pm.queryBroadcastReceiversAsUser(intent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.USER_SYSTEM)) {
+ if (resolveInfo == null || resolveInfo.activityInfo == null) {
+ continue;
+ }
final ActivityInfo activityInfo = resolveInfo.activityInfo;
final int priority = resolveInfo.priority;
visitKeyboardLayoutsInPackage(pm, activityInfo, null, priority, visitor);
}
}
- private void visitKeyboardLayout(String keyboardLayoutDescriptor,
+ private void visitKeyboardLayout(@NonNull String keyboardLayoutDescriptor,
KeyboardLayoutVisitor visitor) {
KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
if (d != null) {
@@ -482,8 +485,8 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
}
}
- private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
- String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
+ private void visitKeyboardLayoutsInPackage(PackageManager pm, @NonNull ActivityInfo receiver,
+ @Nullable String keyboardName, int requestedPriority, KeyboardLayoutVisitor visitor) {
Bundle metaData = receiver.metaData;
if (metaData == null) {
return;
@@ -1415,7 +1418,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return packageName + "/" + receiverName + "/" + keyboardName;
}
- public static KeyboardLayoutDescriptor parse(String descriptor) {
+ public static KeyboardLayoutDescriptor parse(@NonNull String descriptor) {
int pos = descriptor.indexOf('/');
if (pos < 0 || pos + 1 == descriptor.length()) {
return null;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 18b495bfce5d..25095edda5d8 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4328,7 +4328,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@GuardedBy("mUidRulesFirstLock")
private boolean updateUidStateUL(int uid, int procState, long procStateSeq,
@ProcessCapability int capability) {
- Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL");
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL: " + uid + "/"
+ + ActivityManager.procStateToString(procState) + "/" + procStateSeq + "/"
+ + ActivityManager.getCapabilitiesSummary(capability));
try {
final UidState oldUidState = mUidState.get(uid);
if (oldUidState != null && procStateSeq < oldUidState.procStateSeq) {
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 28682e3d916f..37023e14eb41 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -37,8 +37,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.os.Binder;
import android.content.res.Resources;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -360,7 +360,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
String serviceName = getServiceNames()[0];
- validateService(serviceName, false);
+ Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false));
mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
@@ -410,7 +410,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
String serviceName = getServiceNames()[1];
- validateService(serviceName, true);
+ Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true));
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
@@ -457,11 +457,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
};
}
- @GuardedBy("mLock")
- private void validateService(String serviceName, boolean checkIsolated)
+ private static void validateServiceElevated(String serviceName, boolean checkIsolated)
throws RemoteException {
if (TextUtils.isEmpty(serviceName)) {
- throw new RuntimeException("");
+ throw new IllegalArgumentException("Received null/empty service name : " + serviceName);
}
ComponentName serviceComponent = ComponentName.unflattenFromString(
serviceName);
@@ -501,8 +500,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService {
}
}
- @GuardedBy("mLock")
- private boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
+ private static boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 18ba2cf1405e..9ba88aa18ce6 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -45,7 +45,6 @@ import android.util.TimingsTraceLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.SystemServerInitThreadPool;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
@@ -256,41 +255,6 @@ public class AppDataHelper {
}
}
- if (!DexOptHelper.useArtService()) { // ART Service handles this on demand instead.
- // Prepare the application profiles only for upgrades and
- // first boot (so that we don't repeat the same operation at
- // each boot).
- //
- // We only have to cover the upgrade and first boot here
- // because for app installs we prepare the profiles before
- // invoking dexopt (in installPackageLI).
- //
- // We also have to cover non system users because we do not
- // call the usual install package methods for them.
- //
- // NOTE: in order to speed up first boot time we only create
- // the current profile and do not update the content of the
- // reference profile. A system image should already be
- // configured with the right profile keys and the profiles
- // for the speed-profile prebuilds should already be copied.
- // That's done in #performDexOptUpgrade.
- //
- // TODO(calin, mathieuc): We should use .dm files for
- // prebuilds profiles instead of manually copying them in
- // #performDexOptUpgrade. When we do that we should have a
- // more granular check here and only update the existing
- // profiles.
- if (pkg != null && (mPm.isDeviceUpgrading() || mPm.isFirstBoot()
- || (userId != UserHandle.USER_SYSTEM))) {
- try {
- mArtManagerService.prepareAppProfiles(pkg, userId,
- /* updateReferenceProfileContent= */ false);
- } catch (LegacyDexoptDisabledException e2) {
- throw new RuntimeException(e2);
- }
- }
- }
-
final long ceDataInode = createAppDataResult.ceDataInode;
final long deDataInode = createAppDataResult.deDataInode;
@@ -615,15 +579,7 @@ public class AppDataHelper {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- if (DexOptHelper.useArtService()) {
- destroyAppProfilesWithArtService(pkg.getPackageName());
- } else {
- try {
- mArtManagerService.clearAppProfiles(pkg);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ destroyAppProfilesLIF(pkg.getPackageName());
}
public void destroyAppDataLIF(AndroidPackage pkg, int userId, int flags) {
@@ -657,20 +613,6 @@ public class AppDataHelper {
* Destroy ART app profiles for the package.
*/
void destroyAppProfilesLIF(String packageName) {
- if (DexOptHelper.useArtService()) {
- destroyAppProfilesWithArtService(packageName);
- } else {
- try {
- mInstaller.destroyAppProfiles(packageName);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException e) {
- Slog.w(TAG, String.valueOf(e));
- }
- }
- }
-
- private void destroyAppProfilesWithArtService(String packageName) {
if (!DexOptHelper.artManagerLocalIsInitialized()) {
// This function may get called while PackageManagerService is constructed (via e.g.
// InitAppsHelper.initSystemApps), and ART Service hasn't yet been started then (it
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
deleted file mode 100644
index d9452742f99c..000000000000
--- a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-
-/**
- * JobService to run background dex optimization. This is a thin wrapper and most logic exits in
- * {@link BackgroundDexOptService}.
- */
-public final class BackgroundDexOptJobService extends JobService {
-
- @Override
- public boolean onStartJob(JobParameters params) {
- return BackgroundDexOptService.getService().onStartJob(this, params);
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- return BackgroundDexOptService.getService().onStopJob(this, params);
- }
-}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
deleted file mode 100644
index 36677df07ca3..000000000000
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ /dev/null
@@ -1,1152 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
-import static com.android.server.pm.dex.ArtStatsLogUtils.BackgroundDexoptJobStatsLogger;
-
-import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.os.BatteryManagerInternal;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.FunctionalUtils.ThrowingCheckedSupplier;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
-import com.android.server.pm.PackageDexOptimizer.DexOptResult;
-import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.DexoptOptions;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Controls background dex optimization run as idle job or command line.
- */
-public final class BackgroundDexOptService {
- private static final String TAG = "BackgroundDexOptService";
-
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- @VisibleForTesting static final int JOB_IDLE_OPTIMIZE = 800;
- @VisibleForTesting static final int JOB_POST_BOOT_UPDATE = 801;
-
- private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
-
- private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
-
- private static final ComponentName sDexoptServiceName =
- new ComponentName("android", BackgroundDexOptJobService.class.getName());
-
- // Possible return codes of individual optimization steps.
- /** Initial value. */
- public static final int STATUS_UNSPECIFIED = -1;
- /** Ok status: Optimizations finished, All packages were processed, can continue */
- public static final int STATUS_OK = 0;
- /** Optimizations should be aborted. Job scheduler requested it. */
- public static final int STATUS_ABORT_BY_CANCELLATION = 1;
- /** Optimizations should be aborted. No space left on device. */
- public static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
- /** Optimizations should be aborted. Thermal throttling level too high. */
- public static final int STATUS_ABORT_THERMAL = 3;
- /** Battery level too low */
- public static final int STATUS_ABORT_BATTERY = 4;
- /**
- * {@link PackageDexOptimizer#DEX_OPT_FAILED} case. This state means some packages have failed
- * compilation during the job. Note that the failure will not be permanent as the next dexopt
- * job will exclude those failed packages.
- */
- public static final int STATUS_DEX_OPT_FAILED = 5;
- /** Encountered fatal error, such as a runtime exception. */
- public static final int STATUS_FATAL_ERROR = 6;
-
- @IntDef(prefix = {"STATUS_"},
- value =
- {
- STATUS_UNSPECIFIED,
- STATUS_OK,
- STATUS_ABORT_BY_CANCELLATION,
- STATUS_ABORT_NO_SPACE_LEFT,
- STATUS_ABORT_THERMAL,
- STATUS_ABORT_BATTERY,
- STATUS_DEX_OPT_FAILED,
- STATUS_FATAL_ERROR,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Status {}
-
- // Used for calculating space threshold for downgrading unused apps.
- private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
-
- // Thermal cutoff value used if one isn't defined by a system property.
- private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
-
- private final Injector mInjector;
-
- private final DexOptHelper mDexOptHelper;
-
- private final BackgroundDexoptJobStatsLogger mStatsLogger =
- new BackgroundDexoptJobStatsLogger();
-
- private final Object mLock = new Object();
-
- // Thread currently running dexopt. This will be null if dexopt is not running.
- // The thread running dexopt make sure to set this into null when the pending dexopt is
- // completed.
- @GuardedBy("mLock") @Nullable private Thread mDexOptThread;
-
- // Thread currently cancelling dexopt. This thread is in blocked wait state until
- // cancellation is done. Only this thread can change states for control. The other threads, if
- // need to wait for cancellation, should just wait without doing any control.
- @GuardedBy("mLock") @Nullable private Thread mDexOptCancellingThread;
-
- // Tells whether post boot update is completed or not.
- @GuardedBy("mLock") private boolean mFinishedPostBootUpdate;
-
- // True if JobScheduler invocations of dexopt have been disabled.
- @GuardedBy("mLock") private boolean mDisableJobSchedulerJobs;
-
- @GuardedBy("mLock") @Status private int mLastExecutionStatus = STATUS_UNSPECIFIED;
-
- @GuardedBy("mLock") private long mLastExecutionStartUptimeMs;
- @GuardedBy("mLock") private long mLastExecutionDurationMs;
-
- // Keeps packages cancelled from PDO for last session. This is for debugging.
- @GuardedBy("mLock")
- private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
-
- /**
- * Set of failed packages remembered across job runs.
- */
- @GuardedBy("mLock")
- private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
- @GuardedBy("mLock")
- private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
-
- private final long mDowngradeUnusedAppsThresholdInMillis;
-
- private final List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
-
- private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
-
- /** Listener for monitoring package change due to dexopt. */
- public interface PackagesUpdatedListener {
- /** Called when the packages are updated through dexopt */
- void onPackagesUpdated(ArraySet<String> updatedPackages);
- }
-
- public BackgroundDexOptService(Context context, DexManager dexManager, PackageManagerService pm)
- throws LegacyDexoptDisabledException {
- this(new Injector(context, dexManager, pm));
- }
-
- @VisibleForTesting
- public BackgroundDexOptService(Injector injector) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- mInjector = injector;
- mDexOptHelper = mInjector.getDexOptHelper();
- LocalServices.addService(BackgroundDexOptService.class, this);
- mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
- }
-
- /** Start scheduling job after boot completion */
- public void systemReady() throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- if (mInjector.isBackgroundDexOptDisabled()) {
- return;
- }
-
- mInjector.getContext().registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mInjector.getContext().unregisterReceiver(this);
- // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
- // completed.
- scheduleAJob(JOB_POST_BOOT_UPDATE);
- scheduleAJob(JOB_IDLE_OPTIMIZE);
- if (DEBUG) {
- Slog.d(TAG, "BootBgDexopt scheduled");
- }
- }
- }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
- }
-
- /** Dump the current state */
- public void dump(IndentingPrintWriter writer) {
- boolean disabled = mInjector.isBackgroundDexOptDisabled();
- writer.print("enabled:");
- writer.println(!disabled);
- if (disabled) {
- return;
- }
- synchronized (mLock) {
- writer.print("mDexOptThread:");
- writer.println(mDexOptThread);
- writer.print("mDexOptCancellingThread:");
- writer.println(mDexOptCancellingThread);
- writer.print("mFinishedPostBootUpdate:");
- writer.println(mFinishedPostBootUpdate);
- writer.print("mDisableJobSchedulerJobs:");
- writer.println(mDisableJobSchedulerJobs);
- writer.print("mLastExecutionStatus:");
- writer.println(mLastExecutionStatus);
- writer.print("mLastExecutionStartUptimeMs:");
- writer.println(mLastExecutionStartUptimeMs);
- writer.print("mLastExecutionDurationMs:");
- writer.println(mLastExecutionDurationMs);
- writer.print("now:");
- writer.println(SystemClock.elapsedRealtime());
- writer.print("mLastCancelledPackages:");
- writer.println(String.join(",", mLastCancelledPackages));
- writer.print("mFailedPackageNamesPrimary:");
- writer.println(String.join(",", mFailedPackageNamesPrimary));
- writer.print("mFailedPackageNamesSecondary:");
- writer.println(String.join(",", mFailedPackageNamesSecondary));
- }
- }
-
- /** Gets the instance of the service */
- public static BackgroundDexOptService getService() {
- return LocalServices.getService(BackgroundDexOptService.class);
- }
-
- /**
- * Executes the background dexopt job immediately for selected packages or all packages.
- *
- * <p>This is only for shell command and only root or shell user can use this.
- *
- * @param packageNames dex optimize the passed packages in the given order, or all packages in
- * the default order if null
- *
- * @return true if dex optimization is complete. false if the task is cancelled or if there was
- * an error.
- */
- public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames)
- throws LegacyDexoptDisabledException {
- enforceRootOrShell();
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- // Do not cancel and wait for completion if there is pending task.
- waitForDexOptThreadToFinishLocked();
- resetStatesForNewDexOptRunLocked(Thread.currentThread());
- }
- PackageManagerService pm = mInjector.getPackageManagerService();
- List<String> packagesToOptimize;
- if (packageNames == null) {
- packagesToOptimize = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
- } else {
- packagesToOptimize = packageNames;
- }
- return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false);
- } finally {
- Binder.restoreCallingIdentity(identity);
- markDexOptCompleted();
- }
- }
-
- /**
- * Cancels currently running any idle optimization tasks started from JobScheduler
- * or runIdleOptimization call.
- *
- * <p>This is only for shell command and only root or shell user can use this.
- */
- public void cancelBackgroundDexoptJob() throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- enforceRootOrShell();
- Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
- }
-
- /**
- * Sets a flag that disables jobs from being started from JobScheduler.
- *
- * This state is not persistent and is only retained in this service instance.
- *
- * This is intended for shell command use and only root or shell users can call it.
- *
- * @param disable True if JobScheduler invocations should be disabled, false otherwise.
- */
- public void setDisableJobSchedulerJobs(boolean disable) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- enforceRootOrShell();
- synchronized (mLock) {
- mDisableJobSchedulerJobs = disable;
- }
- }
-
- /** Adds listener for package update */
- public void addPackagesUpdatedListener(PackagesUpdatedListener listener)
- throws LegacyDexoptDisabledException {
- // TODO(b/251903639): Evaluate whether this needs to support ART Service or not.
- Installer.checkLegacyDexoptDisabled();
- synchronized (mLock) {
- mPackagesUpdatedListeners.add(listener);
- }
- }
-
- /** Removes package update listener */
- public void removePackagesUpdatedListener(PackagesUpdatedListener listener)
- throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- synchronized (mLock) {
- mPackagesUpdatedListeners.remove(listener);
- }
- }
-
- /**
- * Notifies package change and removes the package from the failed package list so that
- * the package can run dexopt again.
- */
- public void notifyPackageChanged(String packageName) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- // The idle maintenance job skips packages which previously failed to
- // compile. The given package has changed and may successfully compile
- // now. Remove it from the list of known failing packages.
- synchronized (mLock) {
- mFailedPackageNamesPrimary.remove(packageName);
- mFailedPackageNamesSecondary.remove(packageName);
- }
- }
-
- /** For BackgroundDexOptJobService to dispatch onStartJob event */
- /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
- Slog.i(TAG, "onStartJob:" + params.getJobId());
-
- boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
- // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
- // the checks above. This check is not "live" - the value is determined by a background
- // restart with a period of ~1 minute.
- PackageManagerService pm = mInjector.getPackageManagerService();
- if (pm.isStorageLow()) {
- Slog.w(TAG, "Low storage, skipping this run");
- markPostBootUpdateCompleted(params);
- return false;
- }
-
- List<String> pkgs = mDexOptHelper.getOptimizablePackages(pm.snapshotComputer());
- if (pkgs.isEmpty()) {
- Slog.i(TAG, "No packages to optimize");
- markPostBootUpdateCompleted(params);
- return false;
- }
-
- mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
-
- synchronized (mLock) {
- if (mDisableJobSchedulerJobs) {
- Slog.i(TAG, "JobScheduler invocations disabled");
- return false;
- }
- if (mDexOptThread != null && mDexOptThread.isAlive()) {
- // Other task is already running.
- return false;
- }
- if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
- // Post boot job not finished yet. Run post boot job first.
- return false;
- }
- try {
- resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
- "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
- () -> {
- TimingsTraceAndSlog tr =
- new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_DALVIK);
- tr.traceBegin("jobExecution");
- boolean completed = false;
- boolean fatalError = false;
- try {
- completed = runIdleOptimization(
- pm, pkgs, params.getJobId() == JOB_POST_BOOT_UPDATE);
- } catch (LegacyDexoptDisabledException e) {
- Slog.wtf(TAG, e);
- } catch (RuntimeException e) {
- fatalError = true;
- throw e;
- } finally { // Those cleanup should be done always.
- tr.traceEnd();
- Slog.i(TAG,
- "dexopt finishing. jobid:" + params.getJobId()
- + " completed:" + completed);
-
- writeStatsLog(params);
-
- if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
- if (completed) {
- markPostBootUpdateCompleted(params);
- }
- }
- // Reschedule when cancelled. No need to reschedule when failed with
- // fatal error because it's likely to fail again.
- job.jobFinished(params, !completed && !fatalError);
- markDexOptCompleted();
- }
- }));
- } catch (LegacyDexoptDisabledException e) {
- Slog.wtf(TAG, e);
- }
- }
- return true;
- }
-
- /** For BackgroundDexOptJobService to dispatch onStopJob event */
- /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
- Slog.i(TAG, "onStopJob:" + params.getJobId());
- // This cannot block as it is in main thread, thus dispatch to a newly created thread
- // and cancel it from there. As this event does not happen often, creating a new thread
- // is justified rather than having one thread kept permanently.
- mInjector.createAndStartThread("DexOptCancel", () -> {
- try {
- cancelDexOptAndWaitForCompletion();
- } catch (LegacyDexoptDisabledException e) {
- Slog.wtf(TAG, e);
- }
- });
- // Always reschedule for cancellation.
- return true;
- }
-
- /**
- * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
- * until cancellation is done.
- */
- private void cancelDexOptAndWaitForCompletion() throws LegacyDexoptDisabledException {
- synchronized (mLock) {
- if (mDexOptThread == null) {
- return;
- }
- if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
- // No control, just wait
- waitForDexOptThreadToFinishLocked();
- // Do not wait for other cancellation's complete. That will be handled by the next
- // start flow.
- return;
- }
- mDexOptCancellingThread = Thread.currentThread();
- // Take additional caution to make sure that we do not leave this call
- // with controlDexOptBlockingLocked(true) state.
- try {
- controlDexOptBlockingLocked(true);
- waitForDexOptThreadToFinishLocked();
- } finally {
- // Reset to default states regardless of previous states
- mDexOptCancellingThread = null;
- mDexOptThread = null;
- controlDexOptBlockingLocked(false);
- mLock.notifyAll();
- }
- }
- }
-
- @GuardedBy("mLock")
- private void waitForDexOptThreadToFinishLocked() {
- TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
- // This tracing section doesn't have any correspondence in ART Service - it never waits for
- // cancellation to finish.
- tr.traceBegin("waitForDexOptThreadToFinishLocked");
- try {
- // Wait but check in regular internal to see if the thread is still alive.
- while (mDexOptThread != null && mDexOptThread.isAlive()) {
- mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
- }
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted while waiting for dexopt thread");
- Thread.currentThread().interrupt();
- }
- tr.traceEnd();
- }
-
- private void markDexOptCompleted() {
- synchronized (mLock) {
- if (mDexOptThread != Thread.currentThread()) {
- throw new IllegalStateException(
- "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
- + " current:" + Thread.currentThread());
- }
- mDexOptThread = null;
- // Other threads may be waiting for completion.
- mLock.notifyAll();
- }
- }
-
- @GuardedBy("mLock")
- private void resetStatesForNewDexOptRunLocked(Thread thread)
- throws LegacyDexoptDisabledException {
- mDexOptThread = thread;
- mLastCancelledPackages.clear();
- controlDexOptBlockingLocked(false);
- }
-
- private void enforceRootOrShell() {
- int uid = mInjector.getCallingUid();
- if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
- throw new SecurityException("Should be shell or root user");
- }
- }
-
- @GuardedBy("mLock")
- private void controlDexOptBlockingLocked(boolean block) throws LegacyDexoptDisabledException {
- PackageManagerService pm = mInjector.getPackageManagerService();
- mDexOptHelper.controlDexOptBlocking(block);
- }
-
- private void scheduleAJob(int jobId) {
- JobScheduler js = mInjector.getJobScheduler();
- JobInfo.Builder builder =
- new JobInfo.Builder(jobId, sDexoptServiceName).setRequiresDeviceIdle(true);
- if (jobId == JOB_IDLE_OPTIMIZE) {
- builder.setRequiresCharging(true).setPeriodic(IDLE_OPTIMIZATION_PERIOD);
- }
- js.schedule(builder.build());
- }
-
- private long getLowStorageThreshold() {
- long lowThreshold = mInjector.getDataDirStorageLowBytes();
- if (lowThreshold == 0) {
- Slog.e(TAG, "Invalid low storage threshold");
- }
-
- return lowThreshold;
- }
-
- private void logStatus(int status) {
- switch (status) {
- case STATUS_OK:
- Slog.i(TAG, "Idle optimizations completed.");
- break;
- case STATUS_ABORT_NO_SPACE_LEFT:
- Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
- break;
- case STATUS_ABORT_BY_CANCELLATION:
- Slog.w(TAG, "Idle optimizations aborted by cancellation.");
- break;
- case STATUS_ABORT_THERMAL:
- Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
- break;
- case STATUS_ABORT_BATTERY:
- Slog.w(TAG, "Idle optimizations aborted by low battery.");
- break;
- case STATUS_DEX_OPT_FAILED:
- Slog.w(TAG, "Idle optimizations failed from dexopt.");
- break;
- default:
- Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
- break;
- }
- }
-
- /**
- * Returns whether we've successfully run the job. Note that it will return true even if some
- * packages may have failed compiling.
- */
- private boolean runIdleOptimization(PackageManagerService pm, List<String> pkgs,
- boolean isPostBootUpdate) throws LegacyDexoptDisabledException {
- synchronized (mLock) {
- mLastExecutionStatus = STATUS_UNSPECIFIED;
- mLastExecutionStartUptimeMs = SystemClock.uptimeMillis();
- mLastExecutionDurationMs = -1;
- }
-
- int status = STATUS_UNSPECIFIED;
- try {
- long lowStorageThreshold = getLowStorageThreshold();
- status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
- logStatus(status);
- return status == STATUS_OK || status == STATUS_DEX_OPT_FAILED;
- } catch (RuntimeException e) {
- status = STATUS_FATAL_ERROR;
- throw e;
- } finally {
- synchronized (mLock) {
- mLastExecutionStatus = status;
- mLastExecutionDurationMs = SystemClock.uptimeMillis() - mLastExecutionStartUptimeMs;
- }
- }
- }
-
- /** Gets the size of the directory. It uses recursion to go over all files. */
- private long getDirectorySize(File f) {
- long size = 0;
- if (f.isDirectory()) {
- for (File file : f.listFiles()) {
- size += getDirectorySize(file);
- }
- } else {
- size = f.length();
- }
- return size;
- }
-
- /** Gets the size of a package. */
- private long getPackageSize(@NonNull Computer snapshot, String pkg) {
- // TODO(b/251903639): Make this in line with the calculation in
- // `DexOptHelper.DexoptDoneHandler`.
- PackageInfo info = snapshot.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
- long size = 0;
- if (info != null && info.applicationInfo != null) {
- File path = Paths.get(info.applicationInfo.sourceDir).toFile();
- if (path.isFile()) {
- path = path.getParentFile();
- }
- size += getDirectorySize(path);
- if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
- for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- File pathSplitSourceDir = Paths.get(splitSourceDir).toFile();
- if (pathSplitSourceDir.isFile()) {
- pathSplitSourceDir = pathSplitSourceDir.getParentFile();
- }
- if (path.getAbsolutePath().equals(pathSplitSourceDir.getAbsolutePath())) {
- continue;
- }
- size += getDirectorySize(pathSplitSourceDir);
- }
- }
- return size;
- }
- return 0;
- }
-
- @Status
- private int idleOptimizePackages(PackageManagerService pm, List<String> pkgs,
- long lowStorageThreshold, boolean isPostBootUpdate)
- throws LegacyDexoptDisabledException {
- ArraySet<String> updatedPackages = new ArraySet<>();
-
- try {
- boolean supportSecondaryDex = mInjector.supportSecondaryDex();
-
- if (supportSecondaryDex) {
- @Status int result = reconcileSecondaryDexFiles();
- if (result != STATUS_OK) {
- return result;
- }
- }
-
- // Only downgrade apps when space is low on device.
- // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
- // up disk before user hits the actual lowStorageThreshold.
- long lowStorageThresholdForDowngrade =
- LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * lowStorageThreshold;
- boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
- if (DEBUG) {
- Slog.d(TAG, "Should Downgrade " + shouldDowngrade);
- }
- if (shouldDowngrade) {
- final Computer snapshot = pm.snapshotComputer();
- Set<String> unusedPackages =
- snapshot.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
- if (DEBUG) {
- Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
- }
-
- if (!unusedPackages.isEmpty()) {
- for (String pkg : unusedPackages) {
- @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
- if (abortCode != STATUS_OK) {
- // Should be aborted by the scheduler.
- return abortCode;
- }
- @DexOptResult
- int downgradeResult = downgradePackage(snapshot, pm, pkg,
- /* isForPrimaryDex= */ true, isPostBootUpdate);
- if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- }
- @Status
- int status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
- if (status != STATUS_OK) {
- return status;
- }
- if (supportSecondaryDex) {
- downgradeResult = downgradePackage(snapshot, pm, pkg,
- /* isForPrimaryDex= */ false, isPostBootUpdate);
- status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
- if (status != STATUS_OK) {
- return status;
- }
- }
- }
-
- pkgs = new ArrayList<>(pkgs);
- pkgs.removeAll(unusedPackages);
- }
- }
-
- return optimizePackages(pkgs, lowStorageThreshold, updatedPackages, isPostBootUpdate);
- } finally {
- // Always let the pinner service know about changes.
- // TODO(b/251903639): ART Service does this for all dexopts, while the code below only
- // runs for background jobs. We should try to make them behave the same.
- notifyPinService(updatedPackages);
- // Only notify IORap the primary dex opt, because we don't want to
- // invalidate traces unnecessary due to b/161633001 and that it's
- // better to have a trace than no trace at all.
- notifyPackagesUpdated(updatedPackages);
- }
- }
-
- @Status
- private int optimizePackages(List<String> pkgs, long lowStorageThreshold,
- ArraySet<String> updatedPackages, boolean isPostBootUpdate)
- throws LegacyDexoptDisabledException {
- boolean supportSecondaryDex = mInjector.supportSecondaryDex();
-
- // Keep the error if there is any error from any package.
- @Status int status = STATUS_OK;
-
- // Other than cancellation, all packages will be processed even if an error happens
- // in a package.
- for (String pkg : pkgs) {
- int abortCode = abortIdleOptimizations(lowStorageThreshold);
- if (abortCode != STATUS_OK) {
- // Either aborted by the scheduler or no space left.
- return abortCode;
- }
-
- @DexOptResult
- int primaryResult = optimizePackage(pkg, true /* isForPrimaryDex */, isPostBootUpdate);
- if (primaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
- return STATUS_ABORT_BY_CANCELLATION;
- }
- if (primaryResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- } else if (primaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
- status = convertPackageDexOptimizerStatusToInternal(primaryResult);
- }
-
- if (!supportSecondaryDex) {
- continue;
- }
-
- @DexOptResult
- int secondaryResult =
- optimizePackage(pkg, false /* isForPrimaryDex */, isPostBootUpdate);
- if (secondaryResult == PackageDexOptimizer.DEX_OPT_CANCELLED) {
- return STATUS_ABORT_BY_CANCELLATION;
- }
- if (secondaryResult == PackageDexOptimizer.DEX_OPT_FAILED) {
- status = convertPackageDexOptimizerStatusToInternal(secondaryResult);
- }
- }
- return status;
- }
-
- /**
- * Try to downgrade the package to a smaller compilation filter.
- * eg. if the package is in speed-profile the package will be downgraded to verify.
- * @param pm PackageManagerService
- * @param pkg The package to be downgraded.
- * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
- * @return PackageDexOptimizer.DEX_*
- */
- @DexOptResult
- private int downgradePackage(@NonNull Computer snapshot, PackageManagerService pm, String pkg,
- boolean isForPrimaryDex, boolean isPostBootUpdate)
- throws LegacyDexoptDisabledException {
- if (DEBUG) {
- Slog.d(TAG, "Downgrading " + pkg);
- }
- if (isCancelling()) {
- return PackageDexOptimizer.DEX_OPT_CANCELLED;
- }
- int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- String filter = getCompilerFilterForReason(reason);
- int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
-
- if (isProfileGuidedCompilerFilter(filter)) {
- // We don't expect updates in current profiles to be significant here, but
- // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
- // unconditionally enabled for profile guided filters when ART Service is called instead
- // of the legacy PackageDexOptimizer implementation.
- dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
- }
-
- if (!isPostBootUpdate) {
- dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
- }
-
- long package_size_before = getPackageSize(snapshot, pkg);
- int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
- if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
- // This applies for system apps or if packages location is not a directory, i.e.
- // monolithic install.
- if (!pm.canHaveOatDir(snapshot, pkg)) {
- // For apps that don't have the oat directory, instead of downgrading,
- // remove their compiler artifacts from dalvik cache.
- pm.deleteOatArtifactsOfPackage(snapshot, pkg);
- } else {
- result = performDexOptPrimary(pkg, reason, filter, dexoptFlags);
- }
- } else {
- result = performDexOptSecondary(pkg, reason, filter, dexoptFlags);
- }
-
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- final Computer newSnapshot = pm.snapshotComputer();
- FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
- getPackageSize(newSnapshot, pkg), /*aggressive=*/false);
- }
- return result;
- }
-
- @Status
- private int reconcileSecondaryDexFiles() throws LegacyDexoptDisabledException {
- // TODO(calin): should we denylist packages for which we fail to reconcile?
- for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
- if (isCancelling()) {
- return STATUS_ABORT_BY_CANCELLATION;
- }
- mInjector.getDexManager().reconcileSecondaryDexFiles(p);
- }
- return STATUS_OK;
- }
-
- /**
- *
- * Optimize package if needed. Note that there can be no race between
- * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- * @param pkg The package to be downgraded.
- * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
- * @param isPostBootUpdate is post boot update or not.
- * @return PackageDexOptimizer#DEX_OPT_*
- */
- @DexOptResult
- private int optimizePackage(String pkg, boolean isForPrimaryDex, boolean isPostBootUpdate)
- throws LegacyDexoptDisabledException {
- int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
- : PackageManagerService.REASON_BACKGROUND_DEXOPT;
- String filter = getCompilerFilterForReason(reason);
-
- int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
- if (!isPostBootUpdate) {
- dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
- }
-
- if (isProfileGuidedCompilerFilter(filter)) {
- // Ensure DEXOPT_CHECK_FOR_PROFILES_UPDATES is enabled if the filter is profile guided,
- // to replicate behaviour that will be unconditionally enabled when ART Service is
- // called instead of the legacy PackageDexOptimizer implementation.
- dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
- }
-
- // System server share the same code path as primary dex files.
- // PackageManagerService will select the right optimization path for it.
- if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
- return performDexOptPrimary(pkg, reason, filter, dexoptFlags);
- } else {
- return performDexOptSecondary(pkg, reason, filter, dexoptFlags);
- }
- }
-
- @DexOptResult
- private int performDexOptPrimary(String pkg, int reason, String filter, int dexoptFlags)
- throws LegacyDexoptDisabledException {
- DexoptOptions dexoptOptions =
- new DexoptOptions(pkg, reason, filter, /*splitName=*/null, dexoptFlags);
- return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/true,
- () -> mDexOptHelper.performDexOptWithStatus(dexoptOptions));
- }
-
- @DexOptResult
- private int performDexOptSecondary(String pkg, int reason, String filter, int dexoptFlags)
- throws LegacyDexoptDisabledException {
- DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason, filter, /*splitName=*/null,
- dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
- return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/false,
- ()
- -> mDexOptHelper.performDexOpt(dexoptOptions)
- ? PackageDexOptimizer.DEX_OPT_PERFORMED
- : PackageDexOptimizer.DEX_OPT_FAILED);
- }
-
- /**
- * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
- * the package is added to the list of failed packages.
- * Return one of following result:
- * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
- * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
- * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
- * {@link PackageDexOptimizer#DEX_OPT_FAILED}
- */
- @DexOptResult
- private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
- ThrowingCheckedSupplier<Integer, LegacyDexoptDisabledException> performDexOptWrapper)
- throws LegacyDexoptDisabledException {
- ArraySet<String> failedPackageNames;
- synchronized (mLock) {
- failedPackageNames =
- isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
- if (failedPackageNames.contains(pkg)) {
- // Skip previously failing package
- return PackageDexOptimizer.DEX_OPT_SKIPPED;
- }
- }
- int result = performDexOptWrapper.get();
- if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
- synchronized (mLock) {
- failedPackageNames.add(pkg);
- }
- } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
- synchronized (mLock) {
- mLastCancelledPackages.add(pkg);
- }
- }
- return result;
- }
-
- @Status
- private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
- switch (pdoStatus) {
- case PackageDexOptimizer.DEX_OPT_CANCELLED:
- return STATUS_ABORT_BY_CANCELLATION;
- case PackageDexOptimizer.DEX_OPT_FAILED:
- return STATUS_DEX_OPT_FAILED;
- case PackageDexOptimizer.DEX_OPT_PERFORMED:
- case PackageDexOptimizer.DEX_OPT_SKIPPED:
- return STATUS_OK;
- default:
- Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
- new RuntimeException());
- return STATUS_DEX_OPT_FAILED;
- }
- }
-
- /** Evaluate whether or not idle optimizations should continue. */
- @Status
- private int abortIdleOptimizations(long lowStorageThreshold) {
- if (isCancelling()) {
- // JobScheduler requested an early abort.
- return STATUS_ABORT_BY_CANCELLATION;
- }
-
- // Abort background dexopt if the device is in a moderate or stronger thermal throttling
- // state.
- int thermalStatus = mInjector.getCurrentThermalStatus();
- if (DEBUG) {
- Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
- }
- if (thermalStatus >= mThermalStatusCutoff) {
- return STATUS_ABORT_THERMAL;
- }
-
- if (mInjector.isBatteryLevelLow()) {
- return STATUS_ABORT_BATTERY;
- }
-
- long usableSpace = mInjector.getDataDirUsableSpace();
- if (usableSpace < lowStorageThreshold) {
- // Rather bail than completely fill up the disk.
- Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
- return STATUS_ABORT_NO_SPACE_LEFT;
- }
-
- return STATUS_OK;
- }
-
- // Evaluate whether apps should be downgraded.
- private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
- if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
- return true;
- }
-
- return false;
- }
-
- private boolean isCancelling() {
- synchronized (mLock) {
- return mDexOptCancellingThread != null;
- }
- }
-
- private void markPostBootUpdateCompleted(JobParameters params) {
- if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
- return;
- }
- synchronized (mLock) {
- if (!mFinishedPostBootUpdate) {
- mFinishedPostBootUpdate = true;
- }
- }
- // Safe to do this outside lock.
- mInjector.getJobScheduler().cancel(JOB_POST_BOOT_UPDATE);
- }
-
- private void notifyPinService(ArraySet<String> updatedPackages) {
- PinnerService pinnerService = mInjector.getPinnerService();
- if (pinnerService != null) {
- Slog.i(TAG, "Pinning optimized code " + updatedPackages);
- pinnerService.update(updatedPackages, false /* force */);
- }
- }
-
- /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
- private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
- synchronized (mLock) {
- for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
- listener.onPackagesUpdated(updatedPackages);
- }
- }
- }
-
- private void writeStatsLog(JobParameters params) {
- @Status int status;
- long durationMs;
- long durationIncludingSleepMs;
- synchronized (mLock) {
- status = mLastExecutionStatus;
- durationMs = mLastExecutionDurationMs;
- }
-
- mStatsLogger.write(status, params.getStopReason(), durationMs);
- }
-
- /** Injector pattern for testing purpose */
- @VisibleForTesting
- static final class Injector {
- private final Context mContext;
- private final DexManager mDexManager;
- private final PackageManagerService mPackageManagerService;
- private final File mDataDir = Environment.getDataDirectory();
-
- Injector(Context context, DexManager dexManager, PackageManagerService pm) {
- mContext = context;
- mDexManager = dexManager;
- mPackageManagerService = pm;
- }
-
- int getCallingUid() {
- return Binder.getCallingUid();
- }
-
- Context getContext() {
- return mContext;
- }
-
- PackageManagerService getPackageManagerService() {
- return mPackageManagerService;
- }
-
- DexOptHelper getDexOptHelper() {
- return new DexOptHelper(getPackageManagerService());
- }
-
- JobScheduler getJobScheduler() {
- return mContext.getSystemService(JobScheduler.class);
- }
-
- DexManager getDexManager() {
- return mDexManager;
- }
-
- PinnerService getPinnerService() {
- return LocalServices.getService(PinnerService.class);
- }
-
- boolean isBackgroundDexOptDisabled() {
- return SystemProperties.getBoolean(
- "pm.dexopt.disable_bg_dexopt" /* key */, false /* default */);
- }
-
- boolean isBatteryLevelLow() {
- return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
- }
-
- long getDowngradeUnusedAppsThresholdInMillis() {
- String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
- String sysPropValue = SystemProperties.get(sysPropKey);
- if (sysPropValue == null || sysPropValue.isEmpty()) {
- Slog.w(TAG, "SysProp " + sysPropKey + " not set");
- return Long.MAX_VALUE;
- }
- return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
- }
-
- boolean supportSecondaryDex() {
- return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
- }
-
- long getDataDirUsableSpace() {
- return mDataDir.getUsableSpace();
- }
-
- long getDataDirStorageLowBytes() {
- return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
- }
-
- int getCurrentThermalStatus() {
- IThermalService thermalService = IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
- try {
- return thermalService.getCurrentThermalStatus();
- } catch (RemoteException e) {
- return STATUS_ABORT_THERMAL;
- }
- }
-
- int getDexOptThermalCutoff() {
- return SystemProperties.getInt(
- "dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
- }
-
- Thread createAndStartThread(String name, Runnable target) {
- Thread thread = new Thread(target, name);
- Slog.i(TAG, "Starting thread:" + name);
- thread.start();
- return thread;
- }
- }
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b5476fdd3050..9480c8e72402 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -137,7 +137,6 @@ import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.parsing.PackageInfoUtils;
@@ -419,7 +418,6 @@ public class ComputerEngine implements Computer {
private final PackageDexOptimizer mPackageDexOptimizer;
private final DexManager mDexManager;
private final CompilerStats mCompilerStats;
- private final BackgroundDexOptService mBackgroundDexOptService;
private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
private final CrossProfileIntentResolverEngine mCrossProfileIntentResolverEngine;
@@ -472,7 +470,6 @@ public class ComputerEngine implements Computer {
mPackageDexOptimizer = args.service.mPackageDexOptimizer;
mDexManager = args.service.getDexManager();
mCompilerStats = args.service.mCompilerStats;
- mBackgroundDexOptService = args.service.mBackgroundDexOptService;
mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
@@ -3093,40 +3090,7 @@ public class ComputerEngine implements Computer {
}
ipw.println("Dexopt state:");
ipw.increaseIndent();
- if (DexOptHelper.useArtService()) {
- DexOptHelper.dumpDexoptState(ipw, packageName);
- } else {
- Collection<? extends PackageStateInternal> pkgSettings;
- if (setting != null) {
- pkgSettings = Collections.singletonList(setting);
- } else {
- pkgSettings = mSettings.getPackages().values();
- }
-
- for (PackageStateInternal pkgSetting : pkgSettings) {
- final AndroidPackage pkg = pkgSetting.getPkg();
- if (pkg == null || pkg.isApex()) {
- // Skip APEX which is not dex-optimized
- continue;
- }
- final String pkgName = pkg.getPackageName();
- ipw.println("[" + pkgName + "]");
- ipw.increaseIndent();
-
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
- mDexManager.getPackageUseInfoOrDefault(pkgName));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- ipw.decreaseIndent();
- }
- ipw.println("BgDexopt state:");
- ipw.increaseIndent();
- mBackgroundDexOptService.dump(ipw);
- ipw.decreaseIndent();
- }
+ DexOptHelper.dumpDexoptState(ipw, packageName);
ipw.decreaseIndent();
break;
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index ecfc768a874e..51793f65f7a1 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -23,7 +23,6 @@ import static android.os.incremental.IncrementalManager.isIncrementalPath;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.ApexManager.ActiveApexInfo;
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAINLINE_UPDATE;
@@ -32,10 +31,7 @@ import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
-import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
@@ -45,7 +41,6 @@ import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
-import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -56,8 +51,6 @@ import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.dex.ArtManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -83,8 +76,6 @@ import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
@@ -131,228 +122,6 @@ public final class DexOptHelper {
}
/**
- * Performs dexopt on the set of packages in {@code packages} and returns an int array
- * containing statistics about the invocation. The array consists of three elements,
- * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
- * and {@code numberOfPackagesFailed}.
- */
- public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates,
- final int compilationReason, boolean bootComplete)
- throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
- int numberOfPackagesVisited = 0;
- int numberOfPackagesOptimized = 0;
- int numberOfPackagesSkipped = 0;
- int numberOfPackagesFailed = 0;
- final int numberOfPackagesToDexopt = packageStates.size();
-
- for (var packageState : packageStates) {
- var pkg = packageState.getAndroidPackage();
- numberOfPackagesVisited++;
-
- boolean useProfileForDexopt = false;
-
- if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && packageState.isSystem()) {
- // Copy over initial preopt profiles since we won't get any JIT samples for methods
- // that are already compiled.
- File profileFile = new File(getPrebuildProfilePath(pkg));
- // Copy profile if it exists.
- if (profileFile.exists()) {
- try {
- // We could also do this lazily before calling dexopt in
- // PackageDexOptimizer to prevent this happening on first boot. The issue
- // is that we don't have a good way to say "do this only once".
- if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- Log.e(TAG, "Installer failed to copy system profile!");
- } else {
- // Disabled as this causes speed-profile compilation during first boot
- // even if things are already compiled.
- // useProfileForDexopt = true;
- }
- } catch (InstallerException | RuntimeException e) {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
- e);
- }
- } else {
- PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
- pkg.getPackageName());
- // Handle compressed APKs in this path. Only do this for stubs with profiles to
- // minimize the number off apps being speed-profile compiled during first boot.
- // The other paths will not change the filter.
- if (disabledPs != null && disabledPs.getPkg().isStub()) {
- // The package is the stub one, remove the stub suffix to get the normal
- // package and APK names.
- String systemProfilePath = getPrebuildProfilePath(disabledPs.getPkg())
- .replace(STUB_SUFFIX, "");
- profileFile = new File(systemProfilePath);
- // If we have a profile for a compressed APK, copy it to the reference
- // location.
- // Note that copying the profile here will cause it to override the
- // reference profile every OTA even though the existing reference profile
- // may have more data. We can't copy during decompression since the
- // directories are not set up at that point.
- if (profileFile.exists()) {
- try {
- // We could also do this lazily before calling dexopt in
- // PackageDexOptimizer to prevent this happening on first boot. The
- // issue is that we don't have a good way to say "do this only
- // once".
- if (!mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- Log.e(TAG, "Failed to copy system profile for stub package!");
- } else {
- useProfileForDexopt = true;
- }
- } catch (InstallerException | RuntimeException e) {
- Log.e(TAG, "Failed to copy profile "
- + profileFile.getAbsolutePath() + " ", e);
- }
- }
- }
- }
- }
-
- if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
- }
- numberOfPackagesSkipped++;
- continue;
- }
-
- if (DEBUG_DEXOPT) {
- Log.i(TAG, "Updating app " + numberOfPackagesVisited + " of "
- + numberOfPackagesToDexopt + ": " + pkg.getPackageName());
- }
-
- int pkgCompilationReason = compilationReason;
- if (useProfileForDexopt) {
- // Use background dexopt mode to try and use the profile. Note that this does not
- // guarantee usage of the profile.
- pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- }
-
- int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
-
- String filter = getCompilerFilterForReason(pkgCompilationReason);
- if (isProfileGuidedCompilerFilter(filter)) {
- // DEXOPT_CHECK_FOR_PROFILES_UPDATES used to be false to avoid merging profiles
- // during boot which might interfere with background compilation (b/28612421).
- // However those problems were related to the verify-profile compiler filter which
- // doesn't exist any more, so enable it again.
- dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES;
- }
-
- if (compilationReason == REASON_FIRST_BOOT) {
- // TODO: This doesn't cover the upgrade case, we should check for this too.
- dexoptFlags |= DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
- }
- int primaryDexOptStatus = performDexOptTraced(
- new DexoptOptions(pkg.getPackageName(), pkgCompilationReason, filter,
- /*splitName*/ null, dexoptFlags));
-
- switch (primaryDexOptStatus) {
- case PackageDexOptimizer.DEX_OPT_PERFORMED:
- numberOfPackagesOptimized++;
- break;
- case PackageDexOptimizer.DEX_OPT_SKIPPED:
- numberOfPackagesSkipped++;
- break;
- case PackageDexOptimizer.DEX_OPT_CANCELLED:
- // ignore this case
- break;
- case PackageDexOptimizer.DEX_OPT_FAILED:
- numberOfPackagesFailed++;
- break;
- default:
- Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStatus);
- break;
- }
- }
-
- return new int[]{numberOfPackagesOptimized, numberOfPackagesSkipped,
- numberOfPackagesFailed};
- }
-
- /**
- * Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
- * compiles it if needed.
- */
- private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
- Computer snapshot = mPm.snapshotComputer();
- String sysUiPackageName =
- mPm.mContext.getString(com.android.internal.R.string.config_systemUi);
- AndroidPackage pkg = snapshot.getPackage(sysUiPackageName);
- if (pkg == null) {
- Log.w(TAG, "System UI package " + sysUiPackageName + " is not found for dexopting");
- return;
- }
-
- String defaultCompilerFilter = getCompilerFilterForReason(reason);
- String targetCompilerFilter =
- SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
- String compilerFilter;
-
- if (isProfileGuidedCompilerFilter(targetCompilerFilter)) {
- compilerFilter = "verify";
- File profileFile = new File(getPrebuildProfilePath(pkg));
-
- // Copy the profile to the reference profile path if it exists. Installd can only use a
- // profile at the reference profile path for dexopt.
- if (profileFile.exists()) {
- try {
- synchronized (mPm.mInstallLock) {
- if (mPm.mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.getUid(), pkg.getPackageName(),
- ArtManager.getProfileName(null))) {
- compilerFilter = targetCompilerFilter;
- } else {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath());
- }
- }
- } catch (InstallerException | RuntimeException e) {
- Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath(), e);
- }
- }
- } else {
- compilerFilter = targetCompilerFilter;
- }
-
- performDexoptPackage(sysUiPackageName, reason, compilerFilter);
- }
-
- private void dexoptLauncher(int reason) throws LegacyDexoptDisabledException {
- Computer snapshot = mPm.snapshotComputer();
- RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
- for (var packageName : roleManager.getRoleHolders(RoleManager.ROLE_HOME)) {
- AndroidPackage pkg = snapshot.getPackage(packageName);
- if (pkg == null) {
- Log.w(TAG, "Launcher package " + packageName + " is not found for dexopting");
- } else {
- performDexoptPackage(packageName, reason, "speed-profile");
- }
- }
- }
-
- private void performDexoptPackage(@NonNull String packageName, int reason,
- @NonNull String compilerFilter) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
-
- // DEXOPT_CHECK_FOR_PROFILES_UPDATES is set to replicate behaviour that will be
- // unconditionally enabled for profile guided filters when ART Service is called instead of
- // the legacy PackageDexOptimizer implementation.
- int dexoptFlags = isProfileGuidedCompilerFilter(compilerFilter)
- ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- : 0;
-
- performDexOptTraced(new DexoptOptions(
- packageName, reason, compilerFilter, null /* splitName */, dexoptFlags));
- }
-
- /**
* Called during startup to do any boot time dexopting. This can occasionally be time consuming
* (30+ seconds) and the function will block until it is complete.
*/
@@ -377,35 +146,9 @@ public final class DexOptHelper {
final long startTime = System.nanoTime();
- if (useArtService()) {
- mBootDexoptStartTime = startTime;
- getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
- null /* progressCallbackExecutor */, null /* progressCallback */);
- } else {
- try {
- // System UI and the launcher are important to user experience, so we check them
- // after a mainline update or OTA. They may need to be re-compiled in these cases.
- checkAndDexOptSystemUi(reason);
- dexoptLauncher(reason);
-
- if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
- return;
- }
-
- final Computer snapshot = mPm.snapshotComputer();
-
- // TODO(b/251903639): Align this with how ART Service selects packages for boot
- // compilation.
- List<PackageStateInternal> pkgSettings =
- getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
-
- final int[] stats =
- performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
- reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ mBootDexoptStartTime = startTime;
+ getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
+ null /* progressCallbackExecutor */, null /* progressCallback */);
}
private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
@@ -450,15 +193,7 @@ public final class DexOptHelper {
@DexOptResult int dexoptStatus;
if (options.isDexoptOnlySecondaryDex()) {
- if (useArtService()) {
- dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
- } else {
- try {
- return mPm.getDexManager().dexoptSecondaryDex(options);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
} else {
dexoptStatus = performDexOptWithStatus(options);
}
@@ -491,39 +226,11 @@ public final class DexOptHelper {
// if the package can now be considered up to date for the given filter.
@DexOptResult
private int performDexOptInternal(DexoptOptions options) {
- if (useArtService()) {
- return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- }
-
- AndroidPackage p;
- PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- p = mPm.mPackages.get(options.getPackageName());
- pkgSetting = mPm.mSettings.getPackageLPr(options.getPackageName());
- if (p == null || pkgSetting == null) {
- // Package could not be found. Report failure.
- return PackageDexOptimizer.DEX_OPT_FAILED;
- }
- if (p.isApex()) {
- // APEX needs no dexopt
- return PackageDexOptimizer.DEX_OPT_SKIPPED;
- }
- mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
- mPm.mCompilerStats.maybeWriteAsync();
- }
- final long callingId = Binder.clearCallingIdentity();
- try {
- return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
+ return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
}
/**
- * Performs dexopt on the given package using ART Service. May only be called when ART Service
- * is enabled, i.e. when {@link useArtService} returns true.
+ * Performs dexopt on the given package using ART Service.
*/
@DexOptResult
private int performDexOptWithArtService(DexoptOptions options,
@@ -545,91 +252,6 @@ public final class DexOptHelper {
}
}
- @DexOptResult
- private int performDexOptInternalWithDependenciesLI(
- AndroidPackage p, @NonNull PackageStateInternal pkgSetting, DexoptOptions options)
- throws LegacyDexoptDisabledException {
- if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
- // This needs to be done in odrefresh in early boot, for security reasons.
- throw new IllegalArgumentException("Cannot dexopt the system server");
- }
-
- // Select the dex optimizer based on the force parameter.
- // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
- // allocate an object here.
- PackageDexOptimizer pdo = options.isForce()
- ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPm.mPackageDexOptimizer)
- : mPm.mPackageDexOptimizer;
-
- // Dexopt all dependencies first. Note: we ignore the return value and march on
- // on errors.
- // Note that we are going to call performDexOpt on those libraries as many times as
- // they are referenced in packages. When we do a batch of performDexOpt (for example
- // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
- // and the first package that uses the library will dexopt it. The
- // others will see that the compiled code for the library is up to date.
- Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
- final String[] instructionSets = getAppDexInstructionSets(
- pkgSetting.getPrimaryCpuAbi(),
- pkgSetting.getSecondaryCpuAbi());
- if (!deps.isEmpty()) {
- DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
- options.getCompilationReason(), options.getCompilerFilter(),
- options.getSplitName(),
- options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
- for (SharedLibraryInfo info : deps) {
- Computer snapshot = mPm.snapshotComputer();
- AndroidPackage depPackage = snapshot.getPackage(info.getPackageName());
- PackageStateInternal depPackageStateInternal =
- snapshot.getPackageStateInternal(info.getPackageName());
- if (depPackage != null && depPackageStateInternal != null) {
- // TODO: Analyze and investigate if we (should) profile libraries.
- pdo.performDexOpt(depPackage, depPackageStateInternal, instructionSets,
- mPm.getOrCreateCompilerPackageStats(depPackage),
- mPm.getDexManager().getPackageUseInfoOrDefault(
- depPackage.getPackageName()), libraryOptions);
- } else {
- // TODO(ngeoffray): Support dexopting system shared libraries.
- }
- }
- }
-
- return pdo.performDexOpt(p, pkgSetting, instructionSets,
- mPm.getOrCreateCompilerPackageStats(p),
- mPm.getDexManager().getPackageUseInfoOrDefault(p.getPackageName()), options);
- }
-
- /** @deprecated For legacy shell command only. */
- @Deprecated
- public void forceDexOpt(@NonNull Computer snapshot, String packageName)
- throws LegacyDexoptDisabledException {
- PackageManagerServiceUtils.enforceSystemOrRoot("forceDexOpt");
-
- final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
- final AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
- if (packageState == null || pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- if (pkg.isApex()) {
- throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
- }
-
- Trace.traceBegin(TRACE_TAG_DALVIK, "dexopt");
-
- // Whoever is calling forceDexOpt wants a compiled package.
- // Don't use profiles since that may cause compilation to be skipped.
- DexoptOptions options = new DexoptOptions(packageName, REASON_CMDLINE,
- getDefaultCompilerFilter(), null /* splitName */,
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
-
- @DexOptResult int res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
-
- Trace.traceEnd(TRACE_TAG_DALVIK);
- if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
- throw new IllegalStateException("Failed to dexopt: " + res);
- }
- }
-
public boolean performDexOptMode(@NonNull Computer snapshot, String packageName,
String targetCompilerFilter, boolean force, boolean bootComplete, String splitName) {
if (!PackageManagerServiceUtils.isSystemOrRootOrShell()
@@ -872,10 +494,6 @@ public final class DexOptHelper {
}
}
- /*package*/ void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
- mPm.mPackageDexOptimizer.controlDexOptBlocking(block);
- }
-
/**
* Dumps the dexopt state for the given package, or all packages if it is null.
*/
@@ -935,19 +553,9 @@ public final class DexOptHelper {
}
/**
- * Returns true if ART Service should be used for package optimization.
- */
- public static boolean useArtService() {
- return SystemProperties.getBoolean("dalvik.vm.useartservice", false);
- }
-
- /**
* Returns {@link DexUseManagerLocal} if ART Service should be used for package optimization.
*/
public static @Nullable DexUseManagerLocal getDexUseManagerLocal() {
- if (!useArtService()) {
- return null;
- }
try {
return LocalManagerRegistry.getManagerOrThrow(DexUseManagerLocal.class);
} catch (ManagerNotFoundException e) {
@@ -1039,10 +647,6 @@ public final class DexOptHelper {
*/
public static void initializeArtManagerLocal(
@NonNull Context systemContext, @NonNull PackageManagerService pm) {
- if (!useArtService()) {
- return;
- }
-
ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
pm.getDexOptHelper().new DexoptDoneHandler());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index ae68018c90b3..c559892327df 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -46,10 +46,7 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.APP_METADATA_FILE_NAME;
-import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
-import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -173,7 +170,6 @@ import com.android.server.EventLogTags;
import com.android.server.SystemConfig;
import com.android.server.art.model.DexoptResult;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
@@ -272,8 +268,6 @@ final class InstallPackageHelper {
final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
final String realPkgName = request.getRealPackageName();
- final List<String> changedAbiCodePath =
- useArtService() ? null : request.getChangedAbiCodePath();
final PackageSetting pkgSetting;
if (request.getScanRequestPackageSetting() != null) {
SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
@@ -449,23 +443,6 @@ final class InstallPackageHelper {
}
pkgSetting.setSigningDetails(reconciledPkg.mSigningDetails);
- // The conditional on useArtService() for changedAbiCodePath above means this is skipped
- // when ART Service is in use, since it has its own dex file GC.
- if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
- for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
- final String codePathString = changedAbiCodePath.get(i);
- try {
- synchronized (mPm.mInstallLock) {
- mPm.mInstaller.rmdex(codePathString,
- getDexCodeInstructionSet(getPreferredInstructionSet()));
- }
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException ignored) {
- }
- }
- }
-
final int userId = request.getUserId();
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, oldPkgSetting, reconciledPkg);
@@ -2538,20 +2515,6 @@ final class InstallPackageHelper {
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
- // ART Service handles this on demand instead.
- if (!useArtService() && pkg != null) {
- // Prepare the application profiles for the new code paths.
- // This needs to be done before invoking dexopt so that any install-time profile
- // can be used for optimizations.
- try {
- mArtManagerService.prepareAppProfiles(pkg,
- mPm.resolveUserIds(installRequest.getUserId()),
- /* updateReferenceProfileContent= */ true);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
-
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
// Do not run PackageDexOptimizer through the local performDexOpt
@@ -2602,36 +2565,11 @@ final class InstallPackageHelper {
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
- if (useArtService()) {
- DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
- installRequest, dexoptOptions);
- installRequest.onDexoptFinished(dexOptResult);
- } else {
- try {
- mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
- null /* instructionSets */,
- mPm.getOrCreateCompilerPackageStats(pkg),
- mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ DexoptResult dexOptResult =
+ DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
-
- if (!useArtService()) {
- // Notify BackgroundDexOptService that the package has been changed.
- // If this is an update of a package which used to fail to compile,
- // BackgroundDexOptService will remove it from its denylist.
- // ART Service currently doesn't support this and will retry packages in every
- // background dexopt.
- // TODO: Layering violation
- try {
- BackgroundDexOptService.getService().notifyPackageChanged(packageName);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 34903d1ed47d..8038c9a8cb30 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,8 +16,6 @@
package com.android.server.pm;
-import static com.android.server.pm.DexOptHelper.useArtService;
-
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -97,15 +95,6 @@ public class Installer extends SystemService {
*/
public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3;
- /**
- * The results of {@code getOdexVisibility}. See
- * {@link #getOdexVisibility(String, String, String)} for details.
- */
- public static final int ODEX_NOT_FOUND = 0;
- public static final int ODEX_IS_PUBLIC = 1;
- public static final int ODEX_IS_PRIVATE = 2;
-
-
public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE;
public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
@@ -611,37 +600,7 @@ public class Installer extends SystemService {
}
/**
- * Runs dex optimization.
- *
- * @param apkPath Path of target APK
- * @param uid UID of the package
- * @param pkgName Name of the package
- * @param instructionSet Target instruction set to run dex optimization.
- * @param dexoptNeeded Necessary dex optimization for this request. Check
- * {@link dalvik.system.DexFile#NO_DEXOPT_NEEDED},
- * {@link dalvik.system.DexFile#DEX2OAT_FROM_SCRATCH},
- * {@link dalvik.system.DexFile#DEX2OAT_FOR_BOOT_IMAGE}, and
- * {@link dalvik.system.DexFile#DEX2OAT_FOR_FILTER}.
- * @param outputPath Output path of generated dex optimization.
- * @param dexFlags Check {@code DEXOPT_*} for allowed flags.
- * @param compilerFilter Compiler filter like "verify", "speed-profile". Check
- * {@code art/libartbase/base/compiler_filter.cc} for full list.
- * @param volumeUuid UUID of the volume where the package data is stored. {@code null}
- * represents internal storage.
- * @param classLoaderContext This encodes the class loader chain (class loader type + class
- * path) in a format compatible to dex2oat. Check
- * {@code DexoptUtils.processContextForDexLoad} for further details.
- * @param seInfo Selinux context to set for generated outputs.
- * @param downgrade If set, allows downgrading {@code compilerFilter}. If downgrading is not
- * allowed and requested {@code compilerFilter} is considered as downgrade,
- * the request will be ignored.
- * @param targetSdkVersion Target SDK version of the package.
- * @param profileName Name of reference profile file.
- * @param dexMetadataPath Specifies the location of dex metadata file.
- * @param compilationReason Specifies the reason for the compilation like "install".
- * @return {@code true} if {@code dexopt} is completed. {@code false} if it was cancelled.
- *
- * @throws InstallerException if {@code dexopt} fails.
+ * This function only remains to allow overriding in OtaDexoptService.
*/
public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter,
@@ -650,98 +609,7 @@ public class Installer extends SystemService {
@Nullable String profileName, @Nullable String dexMetadataPath,
@Nullable String compilationReason)
throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- assertValidInstructionSet(instructionSet);
- BlockGuard.getVmPolicy().onPathAccess(apkPath);
- BlockGuard.getVmPolicy().onPathAccess(outputPath);
- BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
- if (!checkBeforeRemote()) return false;
- try {
- return mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
- dexFlags, compilerFilter, volumeUuid, classLoaderContext, seInfo, downgrade,
- targetSdkVersion, profileName, dexMetadataPath, compilationReason);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
- * Enables or disables dex optimization blocking.
- *
- * <p> Enabling blocking will also involve cancelling pending dexopt call and killing child
- * processes forked from installd to run dexopt. The pending dexopt call will return false
- * when it is cancelled.
- *
- * @param block set to true to enable blocking / false to disable blocking.
- */
- public void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- try {
- mInstalld.controlDexOptBlocking(block);
- } catch (Exception e) {
- Slog.w(TAG, "blockDexOpt failed", e);
- }
- }
-
- /**
- * Analyzes the ART profiles of the given package, possibly merging the information
- * into the reference profile. Returns whether or not we should optimize the package
- * based on how much information is in the profile.
- *
- * @return one of {@link #PROFILE_ANALYSIS_OPTIMIZE},
- * {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA},
- * {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES}
- */
- public int mergeProfiles(int uid, String packageName, String profileName)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
- try {
- return mInstalld.mergeProfiles(uid, packageName, profileName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
- * Dumps profiles associated with a package in a human readable format.
- */
- public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath,
- boolean dumpClassesAndMethods)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return false;
- BlockGuard.getVmPolicy().onPathAccess(codePath);
- try {
- return mInstalld.dumpProfiles(uid, packageName, profileName, codePath,
- dumpClassesAndMethods);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public boolean copySystemProfile(String systemProfile, int uid, String packageName,
- String profileName) throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return false;
- try {
- return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public void rmdex(String codePath, String instructionSet)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- assertValidInstructionSet(instructionSet);
- if (!checkBeforeRemote()) return;
- BlockGuard.getVmPolicy().onPathAccess(codePath);
- try {
- mInstalld.rmdex(codePath, instructionSet);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
+ throw new LegacyDexoptDisabledException();
}
/**
@@ -757,43 +625,6 @@ public class Installer extends SystemService {
}
}
- public void clearAppProfiles(String packageName, String profileName)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return;
- try {
- mInstalld.clearAppProfiles(packageName, profileName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public void destroyAppProfiles(String packageName)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return;
- try {
- mInstalld.destroyAppProfiles(packageName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
- * Deletes the reference profile with the given name of the given package.
- * @throws InstallerException if the deletion fails.
- */
- public void deleteReferenceProfile(String packageName, String profileName)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return;
- try {
- mInstalld.deleteReferenceProfile(packageName, profileName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
public void createUserData(String uuid, int userId, int userSerial, int flags)
throws InstallerException {
if (!checkBeforeRemote()) return;
@@ -889,40 +720,6 @@ public class Installer extends SystemService {
}
}
- /**
- * Deletes the optimized artifacts generated by ART and returns the number
- * of freed bytes.
- */
- public long deleteOdex(String packageName, String apkPath, String instructionSet,
- String outputPath) throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return -1;
- BlockGuard.getVmPolicy().onPathAccess(apkPath);
- BlockGuard.getVmPolicy().onPathAccess(outputPath);
- try {
- return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
- String[] isas, @Nullable String volumeUuid, int flags)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- for (int i = 0; i < isas.length; i++) {
- assertValidInstructionSet(isas[i]);
- }
- if (!checkBeforeRemote()) return false;
- BlockGuard.getVmPolicy().onPathAccess(apkPath);
- try {
- return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
- volumeUuid, flags);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
@Nullable String volumeUuid, int flags) throws InstallerException {
if (!checkBeforeRemote()) return new byte[0];
@@ -934,28 +731,6 @@ public class Installer extends SystemService {
}
}
- public boolean createProfileSnapshot(int appId, String packageName, String profileName,
- String classpath) throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return false;
- try {
- return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public void destroyProfileSnapshot(String packageName, String profileName)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return;
- try {
- mInstalld.destroyProfileSnapshot(packageName, profileName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
public void invalidateMounts() throws InstallerException {
if (!checkBeforeRemote()) return;
try {
@@ -999,30 +774,6 @@ public class Installer extends SystemService {
}
/**
- * Prepares the app profile for the package at the given path:
- * <ul>
- * <li>Creates the current profile for the given user ID, unless the user ID is
- * {@code UserHandle.USER_NULL}.</li>
- * <li>Merges the profile from the dex metadata file (if present) into the reference
- * profile.</li>
- * </ul>
- */
- public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
- String profileName, String codePath, String dexMetadataPath)
- throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return false;
- BlockGuard.getVmPolicy().onPathAccess(codePath);
- BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
- try {
- return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
- dexMetadataPath);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
* Snapshots user data of the given package.
*
* @param pkg name of the package to snapshot user data for.
@@ -1152,34 +903,6 @@ public class Installer extends SystemService {
}
/**
- * Returns the visibility of the optimized artifacts.
- *
- * @param packageName name of the package.
- * @param apkPath path to the APK.
- * @param instructionSet instruction set of the optimized artifacts.
- * @param outputPath path to the directory that contains the optimized artifacts (i.e., the
- * directory that {@link #dexopt} outputs to).
- *
- * @return {@link #ODEX_NOT_FOUND} if the optimized artifacts are not found, or
- * {@link #ODEX_IS_PUBLIC} if the optimized artifacts are accessible by all apps, or
- * {@link #ODEX_IS_PRIVATE} if the optimized artifacts are only accessible by this app.
- *
- * @throws InstallerException if failed to get the visibility of the optimized artifacts.
- */
- public int getOdexVisibility(String packageName, String apkPath, String instructionSet,
- String outputPath) throws InstallerException, LegacyDexoptDisabledException {
- checkLegacyDexoptDisabled();
- if (!checkBeforeRemote()) return -1;
- BlockGuard.getVmPolicy().onPathAccess(apkPath);
- BlockGuard.getVmPolicy().onPathAccess(outputPath);
- try {
- return mInstalld.getOdexVisibility(packageName, apkPath, instructionSet, outputPath);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- /**
* Returns an auth token for the provided writable FD.
*
* @param authFd a file descriptor to proof that the caller can write to the file.
@@ -1247,14 +970,4 @@ public class Installer extends SystemService {
super("Invalid call to legacy dexopt method while ART Service is in use.");
}
}
-
- /**
- * Throws LegacyDexoptDisabledException if ART Service should be used instead of the
- * {@link android.os.IInstalld} method that follows this method call.
- */
- public static void checkLegacyDexoptDisabled() throws LegacyDexoptDisabledException {
- if (useArtService()) {
- throw new LegacyDexoptDisabledException();
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index c8bc56ce7dcd..85aee8606bc2 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -11,7 +11,6 @@ per-file StagingManager.java = dariofreni@google.com, ioffe@google.com, olilan@g
# dex
per-file AbstractStatsBase.java = file:dex/OWNERS
-per-file BackgroundDexOptService.java = file:dex/OWNERS
per-file CompilerStats.java = file:dex/OWNERS
per-file DexOptHelper.java = file:dex/OWNERS
per-file DynamicCodeLoggingService.java = file:dex/OWNERS
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index ea082cf77987..5b326fd297cb 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -305,13 +304,10 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
throws InstallerException {
final StringBuilder builder = new StringBuilder();
- if (useArtService()) {
- if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
- // installd may change the reference profile in place for secondary dex
- // files, which isn't safe with the lock free approach in ART Service.
- throw new IllegalArgumentException(
- "Invalid OTA dexopt call for secondary dex");
- }
+ if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
+ // installd may change the reference profile in place for secondary dex
+ // files, which isn't safe with the lock free approach in ART Service.
+ throw new IllegalArgumentException("Invalid OTA dexopt call for secondary dex");
}
// The current version. For v10, see b/115993344.
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8a4080ff029d..396fa22393e4 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -18,7 +18,6 @@ package com.android.server.pm;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
-import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
@@ -53,7 +52,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
-import android.os.FileUtils;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -67,7 +65,6 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.F2fsUtils;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.pm.Installer.InstallerException;
@@ -92,7 +89,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Random;
/**
@@ -130,9 +126,8 @@ public class PackageDexOptimizer {
private final Object mInstallLock;
/**
- * This should be accessed only through {@link #getInstallerLI()} with {@link #mInstallLock}
- * or {@link #getInstallerWithoutLock()} without the lock. Check both methods for further
- * details on when to use each of them.
+ * This should be accessed only through {@link #getInstallerLI()} with
+ * {@link #mInstallLock}.
*/
private final Installer mInstaller;
@@ -248,15 +243,6 @@ public class PackageDexOptimizer {
}
/**
- * Cancels currently running dex optimization.
- */
- void controlDexOptBlocking(boolean block) throws LegacyDexoptDisabledException {
- // This method should not hold mInstallLock as cancelling should be possible while
- // the lock is held by other thread running performDexOpt.
- getInstallerWithoutLock().controlDexOptBlocking(block);
- }
-
- /**
* Performs dexopt on all code paths of the given package.
* It assumes the install lock is held.
*/
@@ -334,7 +320,7 @@ public class PackageDexOptimizer {
final boolean isUsedByOtherApps;
if (options.isDexoptAsSharedLibrary()) {
isUsedByOtherApps = true;
- } else if (useArtService()) {
+ } else {
// We get here when collecting dexopt commands in OTA preopt, even when ART Service
// is in use. packageUseInfo isn't useful in that case since the legacy dex use
// database hasn't been updated. So we'd have to query ART Service instead, but it
@@ -342,8 +328,6 @@ public class PackageDexOptimizer {
// That means such apps will get preopted wrong, and we'll leave it to a later
// background dexopt after reboot instead.
isUsedByOtherApps = false;
- } else {
- isUsedByOtherApps = packageUseInfo.isUsedByOtherApps(path);
}
String compilerFilter = getRealCompilerFilter(pkg, options.getCompilerFilter());
@@ -439,12 +423,10 @@ public class PackageDexOptimizer {
}
}
} finally {
+ // ART Service is always enabled, so we should only arrive here
+ // during OTA preopt, and there should be no cloud profile.
if (cloudProfileName != null) {
- try {
- mInstaller.deleteReferenceProfile(pkg.getPackageName(), cloudProfileName);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to cleanup cloud profile", e);
- }
+ throw new LegacyDexoptDisabledException();
}
}
}
@@ -457,30 +439,15 @@ public class PackageDexOptimizer {
*
* @return true on success, or false otherwise.
*/
- @GuardedBy("mInstallLock")
private boolean prepareCloudProfile(AndroidPackage pkg, String profileName, String path,
@Nullable String dexMetadataPath) throws LegacyDexoptDisabledException {
if (dexMetadataPath != null) {
- if (mInstaller.isIsolated()) {
- // If the installer is isolated, the two calls to it below will return immediately,
- // so this only short-circuits that a bit. We need to do it to avoid the
- // LegacyDexoptDisabledException getting thrown first, when we get here during OTA
- // preopt and ART Service is enabled.
- return true;
- }
-
- try {
- // Make sure we don't keep any existing contents.
- mInstaller.deleteReferenceProfile(pkg.getPackageName(), profileName);
-
- final int appId = UserHandle.getAppId(pkg.getUid());
- mInstaller.prepareAppProfile(pkg.getPackageName(), UserHandle.USER_NULL, appId,
- profileName, path, dexMetadataPath);
- return true;
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to prepare cloud profile", e);
- return false;
+ // ART Service is always enabled, so we should only arrive here
+ // during OTA preopt, i.e. when the installer is isolated.
+ if (!mInstaller.isIsolated()) {
+ throw new LegacyDexoptDisabledException();
}
+ return true;
} else {
return false;
}
@@ -554,37 +521,6 @@ public class PackageDexOptimizer {
return getReasonName(compilationReason) + annotation;
}
- /**
- * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
- *
- * @return
- * DEX_OPT_FAILED if there was any exception during dexopt
- * DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
- * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
- * didn't need an update. That's because at the moment we don't get more than success/failure
- * from installd.
- *
- * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
- * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
- * that seems wasteful.
- */
- @DexOptResult
- public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
- PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
- throws LegacyDexoptDisabledException {
- if (info.uid == -1) {
- throw new IllegalArgumentException("Dexopt for path " + path + " has invalid uid.");
- }
- synchronized (mInstallLock) {
- final long acquireTime = acquireWakeLockLI(info.uid);
- try {
- return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options);
- } finally {
- releaseWakeLockLI(acquireTime);
- }
- }
- }
-
@GuardedBy("mInstallLock")
private long acquireWakeLockLI(final int uid) {
// During boot the system doesn't need to instantiate and obtain a wake lock.
@@ -618,69 +554,6 @@ public class PackageDexOptimizer {
}
}
- @GuardedBy("mInstallLock")
- @DexOptResult
- private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
- PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options)
- throws LegacyDexoptDisabledException {
- String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
- dexUseInfo.isUsedByOtherApps());
- // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
- // Secondary dex files are currently not compiled at boot.
- int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
- // Check the app storage and add the appropriate flags.
- if (info.deviceProtectedDataDir != null &&
- FileUtils.contains(info.deviceProtectedDataDir, path)) {
- dexoptFlags |= DEXOPT_STORAGE_DE;
- } else if (info.credentialProtectedDataDir != null &&
- FileUtils.contains(info.credentialProtectedDataDir, path)) {
- dexoptFlags |= DEXOPT_STORAGE_CE;
- } else {
- Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
- return DEX_OPT_FAILED;
- }
- String classLoaderContext = null;
- if (dexUseInfo.isUnsupportedClassLoaderContext()
- || dexUseInfo.isVariableClassLoaderContext()) {
- // If we have an unknown (not yet set), or a variable class loader chain. Just verify
- // the dex file.
- compilerFilter = "verify";
- } else {
- classLoaderContext = dexUseInfo.getClassLoaderContext();
- }
-
- int reason = options.getCompilationReason();
- Log.d(TAG, "Running dexopt on: " + path
- + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
- + " reason=" + getReasonName(reason)
- + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
- + " target-filter=" + compilerFilter
- + " class-loader-context=" + classLoaderContext);
-
- try {
- for (String isa : dexUseInfo.getLoaderIsas()) {
- // Reuse the same dexopt path as for the primary apks. We don't need all the
- // arguments as some (dexopNeeded and oatDir) will be computed by installd because
- // system server cannot read untrusted app content.
- // TODO(calin): maybe add a separate call.
- boolean completed = getInstallerLI().dexopt(path, info.uid, info.packageName,
- isa, /* dexoptNeeded= */ 0,
- /* outputPath= */ null, dexoptFlags,
- compilerFilter, info.volumeUuid, classLoaderContext, info.seInfo,
- options.isDowngrade(), info.targetSdkVersion, /* profileName= */ null,
- /* dexMetadataPath= */ null, getReasonName(reason));
- if (!completed) {
- return DEX_OPT_CANCELLED;
- }
- }
-
- return DEX_OPT_PERFORMED;
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to dexopt", e);
- return DEX_OPT_FAILED;
- }
- }
-
/**
* Adjust the given dexopt-needed value. Can be overridden to influence the decision to
* optimize or not (and in what way).
@@ -697,59 +570,6 @@ public class PackageDexOptimizer {
}
/**
- * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
- */
- void dumpDexoptState(IndentingPrintWriter pw, AndroidPackage pkg,
- PackageStateInternal pkgSetting, PackageDexUsage.PackageUseInfo useInfo)
- throws LegacyDexoptDisabledException {
- final String[] instructionSets = getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
- pkgSetting.getSecondaryCpuAbi());
- final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-
- final List<String> paths = AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
-
- for (String path : paths) {
- pw.println("path: " + path);
- pw.increaseIndent();
-
- for (String isa : dexCodeInstructionSets) {
- try {
- DexFile.OptimizationInfo info = DexFile.getDexFileOptimizationInfo(path, isa);
- pw.println(isa + ": [status=" + info.getStatus()
- +"] [reason=" + info.getReason() + "]");
- } catch (IOException ioe) {
- pw.println(isa + ": [Exception]: " + ioe.getMessage());
- }
- }
-
- if (useInfo.isUsedByOtherApps(path)) {
- pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
- }
-
- Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
-
- if (!dexUseInfoMap.isEmpty()) {
- pw.println("known secondary dex files:");
- pw.increaseIndent();
- for (Map.Entry<String, PackageDexUsage.DexUseInfo> e : dexUseInfoMap.entrySet()) {
- String dex = e.getKey();
- PackageDexUsage.DexUseInfo dexUseInfo = e.getValue();
- pw.println(dex);
- pw.increaseIndent();
- // TODO(calin): get the status of the oat file (needs installd call)
- pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
- if (dexUseInfo.isUsedByOtherApps()) {
- pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
- }
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
- }
-
- /**
* Returns the compiler filter that should be used to optimize the secondary dex.
* The target filter will be updated if the package code is used by other apps
* or if it has the safe mode flag set.
@@ -898,14 +718,13 @@ public class PackageDexOptimizer {
* Assesses if there's a need to perform dexopt on {@code path} for the given
* configuration (isa, compiler filter, profile).
*/
- @GuardedBy("mInstallLock")
private int getDexoptNeeded(String packageName, String path, String isa, String compilerFilter,
String classLoaderContext, int profileAnalysisResult, boolean downgrade,
int dexoptFlags, String oatDir) throws LegacyDexoptDisabledException {
// Allow calls from OtaDexoptService even when ART Service is in use. The installer is
// isolated in that case so later calls to it won't call into installd anyway.
if (!mInstaller.isIsolated()) {
- Installer.checkLegacyDexoptDisabled();
+ throw new LegacyDexoptDisabledException();
}
final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0;
@@ -953,16 +772,9 @@ public class PackageDexOptimizer {
}
/** Returns true if the current artifacts of the app are private to the app itself. */
- @GuardedBy("mInstallLock")
private boolean isOdexPrivate(String packageName, String path, String isa, String oatDir)
throws LegacyDexoptDisabledException {
- try {
- return mInstaller.getOdexVisibility(packageName, path, isa, oatDir)
- == Installer.ODEX_IS_PRIVATE;
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to get odex visibility for " + path, e);
- return false;
- }
+ throw new LegacyDexoptDisabledException();
}
/**
@@ -976,22 +788,7 @@ public class PackageDexOptimizer {
*/
private int analyseProfiles(AndroidPackage pkg, int uid, String profileName,
String compilerFilter) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
-
- // Check if we are allowed to merge and if the compiler filter is profile guided.
- if (!isProfileGuidedCompilerFilter(compilerFilter)) {
- return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
- }
- // Merge profiles. It returns whether or not there was an updated in the profile info.
- try {
- synchronized (mInstallLock) {
- return getInstallerLI().mergeProfiles(uid, pkg.getPackageName(), profileName);
- }
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to merge profiles", e);
- // We don't need to optimize if we failed to merge.
- return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
- }
+ throw new LegacyDexoptDisabledException();
}
/**
@@ -1101,7 +898,7 @@ public class PackageDexOptimizer {
/**
* Returns {@link #mInstaller} with {@link #mInstallLock}. This should be used for all
- * {@link #mInstaller} access unless {@link #getInstallerWithoutLock()} is allowed.
+ * {@link #mInstaller} access.
*/
@GuardedBy("mInstallLock")
private Installer getInstallerLI() {
@@ -1109,14 +906,6 @@ public class PackageDexOptimizer {
}
/**
- * Returns {@link #mInstaller} without lock. This should be used only inside
- * {@link #controlDexOptBlocking(boolean)}.
- */
- private Installer getInstallerWithoutLock() {
- return mInstaller;
- }
-
- /**
* Injector for {@link PackageDexOptimizer} dependencies
*/
interface Injector {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 35cb5b000219..d215822d1b1c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -41,9 +41,6 @@ import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
-import static com.android.server.pm.DexOptHelper.useArtService;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
-import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
@@ -216,10 +213,8 @@ import com.android.server.art.model.DeleteResult;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.ArtManagerService;
-import com.android.server.pm.dex.ArtUtils;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DynamicCodeLogger;
import com.android.server.pm.local.PackageManagerLocalImpl;
@@ -820,8 +815,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// TODO(b/260124949): Remove these.
final PackageDexOptimizer mPackageDexOptimizer;
- @Nullable
- final BackgroundDexOptService mBackgroundDexOptService; // null when ART Service is in use.
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
private final DexManager mDexManager;
@@ -1763,16 +1756,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
new DefaultSystemWrapper(),
LocalServices::getService,
context::getSystemService,
- (i, pm) -> {
- if (useArtService()) {
- return null;
- }
- try {
- return new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- },
(i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
Context.BACKUP_SERVICE)),
(i, pm) -> new SharedLibrariesImpl(pm, i),
@@ -1916,7 +1899,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mApexManager = testParams.apexManager;
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
- mBackgroundDexOptService = testParams.backgroundDexOptService;
mDefParseFlags = testParams.defParseFlags;
mDefaultAppProvider = testParams.defaultAppProvider;
mLegacyPermissionManager = testParams.legacyPermissionManagerInternal;
@@ -2113,7 +2095,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPackageDexOptimizer = injector.getPackageDexOptimizer();
mDexManager = injector.getDexManager();
mDynamicCodeLogger = injector.getDynamicCodeLogger();
- mBackgroundDexOptService = injector.getBackgroundDexOptService();
mArtManagerService = injector.getArtManagerService();
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
mSharedLibraries = mInjector.getSharedLibrariesImpl();
@@ -2369,19 +2350,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
null /*scannedPackage*/,
mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
setting.getPackageStates(), null /*scannedPackage*/));
- if (!useArtService() && // Skip for ART Service since it has its own dex file GC.
- changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
- for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
- final String codePathString = changedAbiCodePath.get(i);
- try {
- mInstaller.rmdex(codePathString,
- getDexCodeInstructionSet(getPreferredInstructionSet()));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (InstallerException ignored) {
- }
- }
- }
// Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
// SELinux domain.
setting.fixSeInfoLocked();
@@ -4309,16 +4277,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
});
- if (!useArtService()) {
- // The background dexopt job is scheduled in DexOptHelper.initializeArtManagerLocal when
- // ART Service is in use.
- try {
- mBackgroundDexOptService.systemReady();
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
-
// Prune unused static shared libraries which have been cached a period of time
schedulePruneUnusedStaticSharedLibraries(false /* delay */);
@@ -6903,46 +6861,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
}
- /** @deprecated For legacy shell command only. */
- @Override
- @Deprecated
- public void legacyDumpProfiles(String packageName, boolean dumpClassesAndMethods)
- throws LegacyDexoptDisabledException {
- final Computer snapshot = snapshotComputer();
- AndroidPackage pkg = snapshot.getPackage(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
-
- synchronized (mInstallLock) {
- Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "dump profiles");
- mArtManagerService.dumpProfiles(pkg, dumpClassesAndMethods);
- Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
- }
- }
-
- /** @deprecated For legacy shell command only. */
- @Override
- @Deprecated
- public void legacyForceDexOpt(String packageName) throws LegacyDexoptDisabledException {
- mDexOptHelper.forceDexOpt(snapshotComputer(), packageName);
- }
-
- /** @deprecated For legacy shell command only. */
- @Override
- @Deprecated
- public void legacyReconcileSecondaryDexFiles(String packageName)
- throws LegacyDexoptDisabledException {
- final Computer snapshot = snapshotComputer();
- if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return;
- } else if (snapshot.isInstantAppInternal(
- packageName, UserHandle.getCallingUserId(), Process.SYSTEM_UID)) {
- return;
- }
- mDexManager.reconcileSecondaryDexFiles(packageName);
- }
-
@Override
@SuppressWarnings("GuardedBy")
public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) {
@@ -7512,33 +7430,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
PackageManagerServiceUtils.enforceSystemOrRootOrShell(
"Only the system or shell can delete oat artifacts");
- if (DexOptHelper.useArtService()) {
- // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
- try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
- PackageManagerServiceUtils.getPackageManagerLocal()
- .withFilteredSnapshot()) {
- try {
- DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
- filteredSnapshot, packageName);
- return res.getFreedBytes();
- } catch (IllegalArgumentException e) {
- Log.e(TAG, e.toString());
- return -1;
- } catch (IllegalStateException e) {
- Slog.wtfStack(TAG, e.toString());
- return -1;
- }
- }
- } else {
- PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
- if (packageState == null || packageState.getPkg() == null) {
- return -1; // error code of deleteOptimizedFiles
- }
+ // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+ try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
try {
- return mDexManager.deleteOptimizedFiles(
- ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+ filteredSnapshot, packageName);
+ return res.getFreedBytes();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.toString());
+ return -1;
+ } catch (IllegalStateException e) {
+ Slog.wtfStack(TAG, e.toString());
+ return -1;
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 049737d42f51..83f3b16b31d1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.backup.IBackupManager;
import android.content.ComponentName;
@@ -138,8 +137,6 @@ public class PackageManagerServiceInjector {
private final Singleton<DomainVerificationManagerInternal>
mDomainVerificationManagerInternalProducer;
private final Singleton<Handler> mHandlerProducer;
- private final Singleton<BackgroundDexOptService>
- mBackgroundDexOptService; // TODO(b/260124949): Remove this.
private final Singleton<IBackupManager> mIBackupManager;
private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer;
private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer;
@@ -180,7 +177,6 @@ public class PackageManagerServiceInjector {
SystemWrapper systemWrapper,
ServiceProducer getLocalServiceProducer,
ServiceProducer getSystemServiceProducer,
- Producer<BackgroundDexOptService> backgroundDexOptService,
Producer<IBackupManager> iBackupManager,
Producer<SharedLibrariesImpl> sharedLibrariesProducer,
Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
@@ -234,7 +230,6 @@ public class PackageManagerServiceInjector {
new Singleton<>(
domainVerificationManagerInternalProducer);
mHandlerProducer = new Singleton<>(handlerProducer);
- mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
mIBackupManager = new Singleton<>(iBackupManager);
mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer);
mCrossProfileIntentFilterHelperProducer = new Singleton<>(
@@ -409,11 +404,6 @@ public class PackageManagerServiceInjector {
return getLocalService(ActivityManagerInternal.class);
}
- @Nullable
- public BackgroundDexOptService getBackgroundDexOptService() {
- return mBackgroundDexOptService.get(this, mPackageManager);
- }
-
public IBackupManager getIBackupManager() {
return mIBackupManager.get(this, mPackageManager);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 2d797187b7f1..289373ee1456 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -105,7 +105,6 @@ public final class PackageManagerServiceTestParams {
public boolean isEngBuild;
public boolean isUserDebugBuild;
public int sdkInt = Build.VERSION.SDK_INT;
- public @Nullable BackgroundDexOptService backgroundDexOptService;
public final String incrementalVersion = Build.VERSION.INCREMENTAL;
public BroadcastHelper broadcastHelper;
public AppDataHelper appDataHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4fb9b56a5f5f..a9e1725ea9a0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -29,7 +29,6 @@ import static android.content.pm.PackageManager.RESTRICTION_NONE;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.accounts.IAccountManager;
import android.annotation.NonNull;
@@ -67,7 +66,6 @@ import android.content.pm.SharedLibraryInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
-import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.content.pm.parsing.ApkLite;
@@ -102,8 +100,6 @@ import android.os.UserManager;
import android.os.incremental.V4Signature;
import android.os.storage.StorageManager;
import android.permission.PermissionManager;
-import android.system.ErrnoException;
-import android.system.Os;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
@@ -123,25 +119,20 @@ import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.art.ArtManagerLocal;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.pm.permission.PermissionAllowlist;
import com.android.server.pm.verify.domain.DomainVerificationShell;
-import dalvik.system.DexFile;
-
import libcore.io.IoUtils;
import libcore.io.Streams;
import libcore.util.HexEncoding;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.security.SecureRandom;
@@ -154,7 +145,6 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
@@ -400,15 +390,7 @@ class PackageManagerShellCommand extends ShellCommand {
return runGetDomainVerificationAgent();
default: {
if (ART_SERVICE_COMMANDS.contains(cmd)) {
- if (DexOptHelper.useArtService()) {
- return runArtServiceCommand();
- } else {
- try {
- return runLegacyDexoptCommand(cmd);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
+ return runArtServiceCommand();
}
Boolean domainVerificationResult =
@@ -438,40 +420,6 @@ class PackageManagerShellCommand extends ShellCommand {
return -1;
}
- private int runLegacyDexoptCommand(@NonNull String cmd)
- throws RemoteException, LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
-
- if (!PackageManagerServiceUtils.isRootOrShell(Binder.getCallingUid())) {
- throw new SecurityException("Dexopt shell commands need root or shell access");
- }
-
- switch (cmd) {
- case "compile":
- return runCompile();
- case "reconcile-secondary-dex-files":
- return runreconcileSecondaryDexFiles();
- case "force-dex-opt":
- return runForceDexOpt();
- case "bg-dexopt-job":
- return runBgDexOpt();
- case "cancel-bg-dexopt-job":
- return cancelBgDexOptJob();
- case "delete-dexopt":
- return runDeleteDexOpt();
- case "dump-profiles":
- return runDumpProfiles();
- case "snapshot-profile":
- return runSnapshotProfile();
- case "art":
- getOutPrintWriter().println("ART Service not enabled");
- return -1;
- default:
- // Can't happen.
- throw new IllegalArgumentException();
- }
- }
-
/**
* Shows module info
*
@@ -2067,340 +2015,6 @@ class PackageManagerShellCommand extends ShellCommand {
}
}
- private int runCompile() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- boolean forceCompilation = false;
- boolean allPackages = false;
- boolean clearProfileData = false;
- String compilerFilter = null;
- String compilationReason = null;
- boolean secondaryDex = false;
- String split = null;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "-a":
- allPackages = true;
- break;
- case "-c":
- clearProfileData = true;
- break;
- case "-f":
- forceCompilation = true;
- break;
- case "-m":
- compilerFilter = getNextArgRequired();
- break;
- case "-r":
- compilationReason = getNextArgRequired();
- break;
- case "--check-prof":
- getNextArgRequired();
- pw.println("Warning: Ignoring obsolete flag --check-prof "
- + "- it is unconditionally enabled now");
- break;
- case "--reset":
- forceCompilation = true;
- clearProfileData = true;
- compilationReason = "install";
- break;
- case "--secondary-dex":
- secondaryDex = true;
- break;
- case "--split":
- split = getNextArgRequired();
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- final boolean compilerFilterGiven = compilerFilter != null;
- final boolean compilationReasonGiven = compilationReason != null;
- // Make sure exactly one of -m, or -r is given.
- if (compilerFilterGiven && compilationReasonGiven) {
- pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
- + "at the same time");
- return 1;
- }
- if (!compilerFilterGiven && !compilationReasonGiven) {
- pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
- + "reason (\"-r\")");
- return 1;
- }
-
- if (allPackages && split != null) {
- pw.println("-a cannot be specified together with --split");
- return 1;
- }
-
- if (secondaryDex && split != null) {
- pw.println("--secondary-dex cannot be specified together with --split");
- return 1;
- }
-
- String targetCompilerFilter = null;
- if (compilerFilterGiven) {
- if (!DexFile.isValidCompilerFilter(compilerFilter)) {
- pw.println("Error: \"" + compilerFilter +
- "\" is not a valid compilation filter.");
- return 1;
- }
- targetCompilerFilter = compilerFilter;
- }
- if (compilationReasonGiven) {
- int reason = -1;
- for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
- if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
- compilationReason)) {
- reason = i;
- break;
- }
- }
- if (reason == -1) {
- pw.println("Error: Unknown compilation reason: " + compilationReason);
- return 1;
- }
- targetCompilerFilter =
- PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
- }
-
-
- List<String> packageNames = null;
- if (allPackages) {
- packageNames = mInterface.getAllPackages();
- // Compiling the system server is only supported from odrefresh, so skip it.
- packageNames.removeIf(packageName -> PLATFORM_PACKAGE_NAME.equals(packageName));
- } else {
- String packageName = getNextArg();
- if (packageName == null) {
- pw.println("Error: package name not specified");
- return 1;
- }
- packageNames = Collections.singletonList(packageName);
- }
-
- List<String> failedPackages = new ArrayList<>();
- int index = 0;
- for (String packageName : packageNames) {
- if (clearProfileData) {
- mInterface.clearApplicationProfileData(packageName);
- }
-
- if (allPackages) {
- pw.println(++index + "/" + packageNames.size() + ": " + packageName);
- pw.flush();
- }
-
- final boolean result = secondaryDex
- ? mInterface.performDexOptSecondary(
- packageName, targetCompilerFilter, forceCompilation)
- : mInterface.performDexOptMode(packageName, true /* checkProfiles */,
- targetCompilerFilter, forceCompilation, true /* bootComplete */, split);
- if (!result) {
- failedPackages.add(packageName);
- }
- }
-
- if (failedPackages.isEmpty()) {
- pw.println("Success");
- return 0;
- } else if (failedPackages.size() == 1) {
- pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
- return 1;
- } else {
- pw.print("Failure: the following packages could not be compiled: ");
- boolean is_first = true;
- for (String packageName : failedPackages) {
- if (is_first) {
- is_first = false;
- } else {
- pw.print(", ");
- }
- pw.print(packageName);
- }
- pw.println();
- return 1;
- }
- }
-
- private int runreconcileSecondaryDexFiles()
- throws RemoteException, LegacyDexoptDisabledException {
- String packageName = getNextArg();
- mPm.legacyReconcileSecondaryDexFiles(packageName);
- return 0;
- }
-
- public int runForceDexOpt() throws RemoteException, LegacyDexoptDisabledException {
- mPm.legacyForceDexOpt(getNextArgRequired());
- return 0;
- }
-
- private int runBgDexOpt() throws RemoteException, LegacyDexoptDisabledException {
- String opt = getNextOption();
-
- if (opt == null) {
- List<String> packageNames = new ArrayList<>();
- String arg;
- while ((arg = getNextArg()) != null) {
- packageNames.add(arg);
- }
- if (!BackgroundDexOptService.getService().runBackgroundDexoptJob(
- packageNames.isEmpty() ? null : packageNames)) {
- getOutPrintWriter().println("Failure");
- return -1;
- }
- } else {
- String extraArg = getNextArg();
- if (extraArg != null) {
- getErrPrintWriter().println("Invalid argument: " + extraArg);
- return -1;
- }
-
- switch (opt) {
- case "--cancel":
- return cancelBgDexOptJob();
-
- case "--disable":
- BackgroundDexOptService.getService().setDisableJobSchedulerJobs(true);
- break;
-
- case "--enable":
- BackgroundDexOptService.getService().setDisableJobSchedulerJobs(false);
- break;
-
- default:
- getErrPrintWriter().println("Unknown option: " + opt);
- return -1;
- }
- }
-
- getOutPrintWriter().println("Success");
- return 0;
- }
-
- private int cancelBgDexOptJob() throws RemoteException, LegacyDexoptDisabledException {
- BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
- getOutPrintWriter().println("Success");
- return 0;
- }
-
- private int runDeleteDexOpt() throws RemoteException {
- PrintWriter pw = getOutPrintWriter();
- String packageName = getNextArg();
- if (TextUtils.isEmpty(packageName)) {
- pw.println("Error: no package name");
- return 1;
- }
- long freedBytes = mPm.deleteOatArtifactsOfPackage(packageName);
- if (freedBytes < 0) {
- pw.println("Error: delete failed");
- return 1;
- }
- pw.println("Success: freed " + freedBytes + " bytes");
- Slog.i(TAG, "delete-dexopt " + packageName + " ,freed " + freedBytes + " bytes");
- return 0;
- }
-
- private int runDumpProfiles() throws RemoteException, LegacyDexoptDisabledException {
- final PrintWriter pw = getOutPrintWriter();
- boolean dumpClassesAndMethods = false;
-
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case "--dump-classes-and-methods":
- dumpClassesAndMethods = true;
- break;
- default:
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
- }
-
- String packageName = getNextArg();
- mPm.legacyDumpProfiles(packageName, dumpClassesAndMethods);
- return 0;
- }
-
- private int runSnapshotProfile() throws RemoteException {
- PrintWriter pw = getOutPrintWriter();
-
- // Parse the arguments
- final String packageName = getNextArg();
- final boolean isBootImage = "android".equals(packageName);
-
- String codePath = null;
- String opt;
- while ((opt = getNextArg()) != null) {
- switch (opt) {
- case "--code-path":
- if (isBootImage) {
- pw.write("--code-path cannot be used for the boot image.");
- return -1;
- }
- codePath = getNextArg();
- break;
- default:
- pw.write("Unknown arg: " + opt);
- return -1;
- }
- }
-
- // If no code path was explicitly requested, select the base code path.
- String baseCodePath = null;
- if (!isBootImage) {
- PackageInfo packageInfo = mInterface.getPackageInfo(packageName, /* flags */ 0,
- /* userId */0);
- if (packageInfo == null) {
- pw.write("Package not found " + packageName);
- return -1;
- }
- baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
- if (codePath == null) {
- codePath = baseCodePath;
- }
- }
-
- // Create the profile snapshot.
- final SnapshotRuntimeProfileCallback callback = new SnapshotRuntimeProfileCallback();
- // The calling package is needed to debug permission access.
- final String callingPackage = (Binder.getCallingUid() == Process.ROOT_UID)
- ? "root" : "com.android.shell";
- final int profileType = isBootImage
- ? ArtManager.PROFILE_BOOT_IMAGE : ArtManager.PROFILE_APPS;
- if (!mInterface.getArtManager().isRuntimeProfilingEnabled(profileType, callingPackage)) {
- pw.println("Error: Runtime profiling is not enabled");
- return -1;
- }
- mInterface.getArtManager().snapshotRuntimeProfile(profileType, packageName,
- codePath, callback, callingPackage);
- if (!callback.waitTillDone()) {
- pw.println("Error: callback not called");
- return callback.mErrCode;
- }
-
- // Copy the snapshot profile to the output profile file.
- try (InputStream inStream = new AutoCloseInputStream(callback.mProfileReadFd)) {
- final String outputFileSuffix = isBootImage || Objects.equals(baseCodePath, codePath)
- ? "" : ("-" + new File(codePath).getName());
- final String outputProfilePath =
- ART_PROFILE_SNAPSHOT_DEBUG_LOCATION + packageName + outputFileSuffix + ".prof";
- try (OutputStream outStream = new FileOutputStream(outputProfilePath)) {
- Streams.copy(inStream, outStream);
- }
- // Give read permissions to the other group.
- Os.chmod(outputProfilePath, /*mode*/ DEFAULT_FILE_ACCESS_MODE);
- } catch (IOException | ErrnoException e) {
- pw.println("Error when reading the profile fd: " + e.getMessage());
- e.printStackTrace(pw);
- return -1;
- }
- return 0;
- }
-
private ArrayList<String> getRemainingArgs() {
ArrayList<String> args = new ArrayList<>();
String arg;
@@ -5212,11 +4826,7 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" get-domain-verification-agent");
pw.println(" Displays the component name of the domain verification agent on device.");
pw.println("");
- if (DexOptHelper.useArtService()) {
- printArtServiceHelp();
- } else {
- printLegacyDexoptHelp();
- }
+ printArtServiceHelp();
pw.println("");
mDomainVerificationShell.printHelp(pw);
pw.println("");
@@ -5235,75 +4845,6 @@ class PackageManagerShellCommand extends ShellCommand {
ipw.decreaseIndent();
}
- private void printLegacyDexoptHelp() {
- final PrintWriter pw = getOutPrintWriter();
- pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
- pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
- pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\". Options are:");
- pw.println(" -a: compile all packages");
- pw.println(" -c: clear profile data before compiling");
- pw.println(" -f: force compilation even if not needed");
- pw.println(" -m: select compilation mode");
- pw.println(" MODE is one of the dex2oat compiler filters:");
- pw.println(" verify");
- pw.println(" speed-profile");
- pw.println(" speed");
- pw.println(" -r: select compilation reason");
- pw.println(" REASON is one of:");
- for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
- pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]);
- }
- pw.println(" --reset: restore package to its post-install state");
- pw.println(" --check-prof (true | false): ignored - this is always true");
- pw.println(" --secondary-dex: compile app secondary dex files");
- pw.println(" --split SPLIT: compile only the given split name");
- pw.println("");
- pw.println(" force-dex-opt PACKAGE");
- pw.println(" Force immediate execution of dex opt for the given PACKAGE.");
- pw.println("");
- pw.println(" delete-dexopt PACKAGE");
- pw.println(" Delete dex optimization results for the given PACKAGE.");
- pw.println("");
- pw.println(" bg-dexopt-job [PACKAGE... | --cancel | --disable | --enable]");
- pw.println(" Controls the background job that optimizes dex files:");
- pw.println(" Without flags, run background optimization immediately on the given");
- pw.println(" PACKAGEs, or all packages if none is specified, and wait until the job");
- pw.println(" finishes. Note that the command only runs the background optimizer logic.");
- pw.println(" It will run even if the device is not in the idle maintenance mode. If a");
- pw.println(" job is already running (including one started automatically by the");
- pw.println(" system) it will wait for it to finish before starting. A background job");
- pw.println(" will not be started automatically while one started this way is running.");
- pw.println(" --cancel: Cancels any currently running background optimization job");
- pw.println(" immediately. This cancels jobs started either automatically by the");
- pw.println(" system or through this command. Note that cancelling a currently");
- pw.println(" running bg-dexopt-job command requires running this command from a");
- pw.println(" separate adb shell.");
- pw.println(" --disable: Disables background jobs from being started by the job");
- pw.println(" scheduler. Does not affect bg-dexopt-job invocations from the shell.");
- pw.println(" Does not imply --cancel. This state will be lost when the");
- pw.println(" system_server process exits.");
- pw.println(" --enable: Enables background jobs to be started by the job scheduler");
- pw.println(" again, if previously disabled by --disable.");
- pw.println(" cancel-bg-dexopt-job");
- pw.println(" Same as bg-dexopt-job --cancel.");
- pw.println("");
- pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
- pw.println(" Reconciles the package secondary dex files with the generated oat files.");
- pw.println("");
- pw.println(" dump-profiles [--dump-classes-and-methods] TARGET-PACKAGE");
- pw.println(" Dumps method/class profile files to");
- pw.println(" " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION
- + "TARGET-PACKAGE-primary.prof.txt.");
- pw.println(" --dump-classes-and-methods: passed along to the profman binary to");
- pw.println(" switch to the format used by 'profman --create-profile-from'.");
- pw.println("");
- pw.println(" snapshot-profile TARGET-PACKAGE [--code-path path]");
- pw.println(" Take a snapshot of the package profiles to");
- pw.println(" " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION
- + "TARGET-PACKAGE[-code-path].prof");
- pw.println(" If TARGET-PACKAGE=android it will take a snapshot of the boot image");
- }
-
private static class LocalIntentReceiver {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 70352be01096..3a0f7fb4b432 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -23,7 +23,6 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
-import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
@@ -49,7 +48,6 @@ import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.pkg.component.ParsedInstrumentation;
import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -263,11 +261,6 @@ final class RemovePackageHelper {
// Step 1: always destroy app profiles.
mAppDataHelper.destroyAppProfilesLIF(packageName);
- // Everything else is preserved if the DELETE_KEEP_DATA flag is on
- if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
- return;
- }
-
final AndroidPackage pkg;
final SharedUserSetting sus;
synchronized (mPm.mLock) {
@@ -284,9 +277,20 @@ final class RemovePackageHelper {
resolvedPkg = PackageImpl.buildFakeForDeletion(packageName, ps.getVolumeUuid());
}
+ int appDataDeletionFlags = FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL;
+ // Personal data is preserved if the DELETE_KEEP_DATA flag is on
+ if ((flags & PackageManager.DELETE_KEEP_DATA) != 0) {
+ if ((flags & PackageManager.DELETE_ARCHIVE) != 0) {
+ mAppDataHelper.clearAppDataLIF(resolvedPkg, userId,
+ appDataDeletionFlags | Installer.FLAG_CLEAR_CACHE_ONLY);
+ mAppDataHelper.clearAppDataLIF(resolvedPkg, userId,
+ appDataDeletionFlags | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ }
+ return;
+ }
+
// Step 2: destroy app data.
- mAppDataHelper.destroyAppDataLIF(resolvedPkg, userId,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
+ mAppDataHelper.destroyAppDataLIF(resolvedPkg, userId, appDataDeletionFlags);
if (userId != UserHandle.USER_ALL) {
ps.setCeDataInode(-1, userId);
ps.setDeDataInode(-1, userId);
@@ -511,32 +515,9 @@ final class RemovePackageHelper {
}
removeCodePathLI(codeFile);
- removeDexFilesLI(allCodePaths, instructionSets);
- }
- @GuardedBy("mPm.mInstallLock")
- private void removeDexFilesLI(@NonNull List<String> allCodePaths,
- @Nullable String[] instructionSets) {
- if (!allCodePaths.isEmpty()) {
- if (instructionSets == null) {
- throw new IllegalStateException("instructionSet == null");
- }
- // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
- // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
- if (!DexOptHelper.useArtService()) {
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String codePath : allCodePaths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- try {
- mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException ignored) {
- }
- }
- }
- }
- }
+ // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
+ // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
}
void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index ae47aa823245..e49dc8250bc7 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -18,7 +18,6 @@ package com.android.server.pm.dex;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -28,7 +27,6 @@ import android.content.pm.PackageManager;
import android.content.pm.dex.ArtManager;
import android.content.pm.dex.ArtManager.ProfileType;
import android.content.pm.dex.ArtManagerInternal;
-import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.content.pm.dex.PackageOptimizationInfo;
import android.os.Binder;
@@ -39,8 +37,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.system.Os;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -53,22 +49,17 @@ import com.android.server.LocalServices;
import com.android.server.art.ArtManagerLocal;
import com.android.server.pm.DexOptHelper;
import com.android.server.pm.Installer;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceCompilerMapping;
import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
@@ -259,91 +250,27 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
// All good, create the profile snapshot.
- if (DexOptHelper.useArtService()) {
- ParcelFileDescriptor fd;
-
- try (PackageManagerLocal.FilteredSnapshot snapshot =
- PackageManagerServiceUtils.getPackageManagerLocal()
- .withFilteredSnapshot()) {
- fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
- snapshot, packageName, splitName);
- } catch (IllegalArgumentException e) {
- // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
- // we've checked them above this can only happen due to race, i.e. the package got
- // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
- // for the split.
- // TODO(mast): Reuse the same snapshot to avoid this race.
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
- return;
- } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- return;
- }
-
- postSuccess(packageName, fd, callback);
- } else {
- int appId = UserHandle.getAppId(info.applicationInfo.uid);
- if (appId < 0) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
- return;
- }
-
- try {
- createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
- appId, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private void createProfileSnapshot(String packageName, String profileName, String classpath,
- int appId, ISnapshotRuntimeProfileCallback callback)
- throws LegacyDexoptDisabledException {
- // Ask the installer to snapshot the profile.
- try {
- if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- return;
- }
- } catch (InstallerException e) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ ParcelFileDescriptor fd;
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
+ snapshot, packageName, splitName);
+ } catch (IllegalArgumentException e) {
+ // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
+ // we've checked them above this can only happen due to race, i.e. the package got
+ // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
+ // for the split.
+ // TODO(mast): Reuse the same snapshot to avoid this race.
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
return;
- }
-
- // Open the snapshot and invoke the callback.
- File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
-
- ParcelFileDescriptor fd = null;
- try {
- fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
- if (fd == null || !fd.getFileDescriptor().valid()) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- } else {
- postSuccess(packageName, fd, callback);
- }
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
- + snapshotProfile, e);
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- }
- }
-
- private void destroyProfileSnapshot(String packageName, String profileName)
- throws LegacyDexoptDisabledException {
- if (DEBUG) {
- Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
+ return;
}
- try {
- mInstaller.destroyProfileSnapshot(packageName, profileName);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
- e);
- }
+ postSuccess(packageName, fd, callback);
}
@Override
@@ -368,42 +295,19 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
- if (DexOptHelper.useArtService()) {
- ParcelFileDescriptor fd;
-
- try (PackageManagerLocal.FilteredSnapshot snapshot =
- PackageManagerServiceUtils.getPackageManagerLocal()
- .withFilteredSnapshot()) {
- fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
- } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
- postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
- ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- return;
- }
-
- postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
- } else {
- // Combine the profiles for boot classpath and system server classpath.
- // This avoids having yet another type of profiles and simplifies the processing.
- String classpath = String.join(
- ":", Os.getenv("BOOTCLASSPATH"), Os.getenv("SYSTEMSERVERCLASSPATH"));
-
- final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
- if (standaloneSystemServerJars != null) {
- classpath = String.join(":", classpath, standaloneSystemServerJars);
- }
-
- try {
- // Create the snapshot.
- createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME,
- classpath,
- /*appId*/ -1, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ ParcelFileDescriptor fd;
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+ postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
+ ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
}
+
+ postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
}
/**
@@ -451,117 +355,6 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
});
}
- /**
- * Prepare the application profiles.
- * For all code paths:
- * - create the current primary profile to save time at app startup time.
- * - copy the profiles from the associated dex metadata file to the reference profile.
- */
- public void prepareAppProfiles(AndroidPackage pkg, @UserIdInt int user,
- boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
- final int appId = UserHandle.getAppId(pkg.getUid());
- if (user < 0) {
- Slog.wtf(TAG, "Invalid user id: " + user);
- return;
- }
- if (appId < 0) {
- Slog.wtf(TAG, "Invalid app id: " + appId);
- return;
- }
- try {
- ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
- for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
- String codePath = codePathsProfileNames.keyAt(i);
- String profileName = codePathsProfileNames.valueAt(i);
- String dexMetadataPath = null;
- // Passing the dex metadata file to the prepare method will update the reference
- // profile content. As such, we look for the dex metadata file only if we need to
- // perform an update.
- if (updateReferenceProfileContent) {
- File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
- dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
- }
- synchronized (mInstaller) {
- boolean result = mInstaller.prepareAppProfile(pkg.getPackageName(), user, appId,
- profileName, codePath, dexMetadataPath);
- if (!result) {
- Slog.e(TAG, "Failed to prepare profile for " +
- pkg.getPackageName() + ":" + codePath);
- }
- }
- }
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to prepare profile for " + pkg.getPackageName(), e);
- }
- }
-
- /**
- * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
- */
- public void prepareAppProfiles(AndroidPackage pkg, int[] user,
- boolean updateReferenceProfileContent) throws LegacyDexoptDisabledException {
- for (int i = 0; i < user.length; i++) {
- prepareAppProfiles(pkg, user[i], updateReferenceProfileContent);
- }
- }
-
- /**
- * Clear the profiles for the given package.
- */
- public void clearAppProfiles(AndroidPackage pkg) throws LegacyDexoptDisabledException {
- try {
- ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
- for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
- String profileName = packageProfileNames.valueAt(i);
- mInstaller.clearAppProfiles(pkg.getPackageName(), profileName);
- }
- } catch (InstallerException e) {
- Slog.w(TAG, String.valueOf(e));
- }
- }
-
- /**
- * Dumps the profiles for the given package.
- */
- public void dumpProfiles(AndroidPackage pkg, boolean dumpClassesAndMethods)
- throws LegacyDexoptDisabledException {
- final int sharedGid = UserHandle.getSharedAppGid(pkg.getUid());
- try {
- ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
- for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
- String codePath = packageProfileNames.keyAt(i);
- String profileName = packageProfileNames.valueAt(i);
- mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath,
- dumpClassesAndMethods);
- }
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to dump profiles", e);
- }
- }
-
- /**
- * Build the profiles names for all the package code paths (excluding resource only paths).
- * Return the map [code path -> profile name].
- */
- private ArrayMap<String, String> getPackageProfileNames(AndroidPackage pkg) {
- ArrayMap<String, String> result = new ArrayMap<>();
- if (pkg.isDeclaredHavingCode()) {
- result.put(pkg.getBaseApkPath(), ArtManager.getProfileName(null));
- }
-
- String[] splitCodePaths = pkg.getSplitCodePaths();
- int[] splitFlags = pkg.getSplitFlags();
- String[] splitNames = pkg.getSplitNames();
- if (!ArrayUtils.isEmpty(splitCodePaths)) {
- for (int i = 0; i < splitCodePaths.length; i++) {
- if ((splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
- result.put(splitCodePaths[i], ArtManager.getProfileName(splitNames[i]));
- }
- }
- }
- return result;
- }
-
// Constants used for logging compilation filter to TRON.
// DO NOT CHANGE existing values.
//
@@ -792,6 +585,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
String packageName, String activityName, long version) {
// For example: /data/misc/iorapd/com.google.android.GoogleCamera/
// 60092239/com.android.camera.CameraLauncher/compiled_traces/compiled_trace.pb
+ // TODO(b/258223472): Clean up iorap code.
Path tracePath = Paths.get(IORAP_DIR,
packageName,
Long.toString(version),
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 57f4a5ddb2bd..a24a2318d423 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -22,13 +22,11 @@ import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILATI
import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK;
import static com.android.internal.art.ArtStatsLog.ART_DATUM_REPORTED__COMPILE_FILTER__ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK;
-import android.app.job.JobParameters;
import android.os.SystemClock;
import android.util.Slog;
import android.util.jar.StrictJarFile;
import com.android.internal.art.ArtStatsLog;
-import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.PackageManagerService;
import java.io.IOException;
@@ -303,42 +301,4 @@ public class ArtStatsLogUtils {
ArtStatsLog.ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_UNKNOWN);
}
}
-
- private static final Map<Integer, Integer> STATUS_MAP =
- Map.of(BackgroundDexOptService.STATUS_UNSPECIFIED,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN,
- BackgroundDexOptService.STATUS_OK,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
- BackgroundDexOptService.STATUS_ABORT_BY_CANCELLATION,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BY_CANCELLATION,
- BackgroundDexOptService.STATUS_ABORT_NO_SPACE_LEFT,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_NO_SPACE_LEFT,
- BackgroundDexOptService.STATUS_ABORT_THERMAL,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_THERMAL,
- BackgroundDexOptService.STATUS_ABORT_BATTERY,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORT_BATTERY,
- BackgroundDexOptService.STATUS_DEX_OPT_FAILED,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_JOB_FINISHED,
- BackgroundDexOptService.STATUS_FATAL_ERROR,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_FATAL_ERROR);
-
- /** Helper class to write background dexopt job stats to statsd. */
- public static class BackgroundDexoptJobStatsLogger {
- /** Writes background dexopt job stats to statsd. */
- public void write(@BackgroundDexOptService.Status int status,
- @JobParameters.StopReason int cancellationReason,
- long durationMs) {
- ArtStatsLog.write(
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED,
- STATUS_MAP.getOrDefault(status,
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
- cancellationReason,
- durationMs,
- 0, // deprecated, used to be durationIncludingSleepMs
- 0, // optimizedPackagesCount
- 0, // packagesDependingOnBootClasspathCount
- 0, // totalPackagesCount
- ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__PASS__PASS_UNKNOWN);
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 78c13f854fe4..e93d3206a4f1 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -17,7 +17,6 @@
package com.android.server.pm.dex;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static java.util.function.Function.identity;
@@ -31,12 +30,9 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.os.BatteryManager;
-import android.os.FileUtils;
import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.util.Log;
import android.util.Slog;
import android.util.jar.StrictJarFile;
@@ -44,8 +40,6 @@ import android.util.jar.StrictJarFile;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.Installer;
-import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.PackageDexOptimizer;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
@@ -54,8 +48,6 @@ import dalvik.system.VMRuntime;
import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -496,60 +488,6 @@ public class DexManager {
}
/**
- * Perform dexopt on with the given {@code options} on the secondary dex files.
- * @return true if all secondary dex files were processed successfully (compiled or skipped
- * because they don't need to be compiled)..
- */
- public boolean dexoptSecondaryDex(DexoptOptions options) throws LegacyDexoptDisabledException {
- if (isPlatformPackage(options.getPackageName())) {
- // We could easily redirect to #dexoptSystemServer in this case. But there should be
- // no-one calling this method directly for system server.
- // As such we prefer to abort in this case.
- Slog.wtf(TAG, "System server jars should be optimized with dexoptSystemServer");
- return false;
- }
-
- PackageDexOptimizer pdo = getPackageDexOptimizer(options);
- String packageName = options.getPackageName();
- PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo.getDexUseInfoMap().isEmpty()) {
- if (DEBUG) {
- Slog.d(TAG, "No secondary dex use for package:" + packageName);
- }
- // Nothing to compile, return true.
- return true;
- }
- boolean success = true;
- for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
- String dexPath = entry.getKey();
- DexUseInfo dexUseInfo = entry.getValue();
-
- PackageInfo pkg;
- try {
- pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
- dexUseInfo.getOwnerUserId());
- } catch (RemoteException e) {
- throw new AssertionError(e);
- }
- // It may be that the package gets uninstalled while we try to compile its
- // secondary dex files. If that's the case, just ignore.
- // Note that we don't break the entire loop because the package might still be
- // installed for other users.
- if (pkg == null) {
- Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
- + " for user " + dexUseInfo.getOwnerUserId());
- mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
- continue;
- }
-
- int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
- dexUseInfo, options);
- success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
- }
- return success;
- }
-
- /**
* Select the dex optimizer based on the force parameter.
* Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
* the necessary dexopt flags to make sure that compilation is not skipped. This avoid
@@ -564,101 +502,6 @@ public class DexManager {
}
/**
- * Reconcile the information we have about the secondary dex files belonging to
- * {@code packagName} and the actual dex files. For all dex files that were
- * deleted, update the internal records and delete any generated oat files.
- */
- public void reconcileSecondaryDexFiles(String packageName)
- throws LegacyDexoptDisabledException {
- PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
- if (useInfo.getDexUseInfoMap().isEmpty()) {
- if (DEBUG) {
- Slog.d(TAG, "No secondary dex use for package:" + packageName);
- }
- // Nothing to reconcile.
- return;
- }
-
- boolean updated = false;
- for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
- String dexPath = entry.getKey();
- DexUseInfo dexUseInfo = entry.getValue();
- PackageInfo pkg = null;
- try {
- // Note that we look for the package in the PackageManager just to be able
- // to get back the real app uid and its storage kind. These are only used
- // to perform extra validation in installd.
- // TODO(calin): maybe a bit overkill.
- pkg = getPackageManager().getPackageInfo(packageName, /*flags*/0,
- dexUseInfo.getOwnerUserId());
- } catch (RemoteException ignore) {
- // Can't happen, DexManager is local.
- }
- if (pkg == null) {
- // It may be that the package was uninstalled while we process the secondary
- // dex files.
- Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
- + " for user " + dexUseInfo.getOwnerUserId());
- // Update the usage and continue, another user might still have the package.
- updated = mPackageDexUsage.removeUserPackage(
- packageName, dexUseInfo.getOwnerUserId()) || updated;
- continue;
- }
-
- // Special handle system server files.
- // We don't need an installd call because we have permissions to check if the file
- // exists.
- if (isPlatformPackage(packageName)) {
- if (!Files.exists(Paths.get(dexPath))) {
- if (DEBUG) {
- Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
- + " anymore: " + dexPath);
- }
- updated = mPackageDexUsage.removeUserPackage(
- packageName, dexUseInfo.getOwnerUserId()) || updated;
- }
- continue;
- }
-
- // This is a regular application.
- ApplicationInfo info = pkg.applicationInfo;
- int flags = 0;
- if (info.deviceProtectedDataDir != null &&
- FileUtils.contains(info.deviceProtectedDataDir, dexPath)) {
- flags |= StorageManager.FLAG_STORAGE_DE;
- } else if (info.credentialProtectedDataDir!= null &&
- FileUtils.contains(info.credentialProtectedDataDir, dexPath)) {
- flags |= StorageManager.FLAG_STORAGE_CE;
- } else {
- Slog.e(TAG, "Could not infer CE/DE storage for path " + dexPath);
- updated = mPackageDexUsage.removeDexFile(
- packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
- continue;
- }
-
- boolean dexStillExists = true;
- synchronized(mInstallLock) {
- try {
- String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
- dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
- info.uid, isas, info.volumeUuid, flags);
- } catch (InstallerException e) {
- Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
- " : " + e.getMessage());
- }
- }
- if (!dexStillExists) {
- updated = mPackageDexUsage.removeDexFile(
- packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
- }
-
- }
- if (updated) {
- mPackageDexUsage.maybeWriteAsync();
- }
- }
-
- /**
* Return all packages that contain records of secondary dex files.
*/
public Set<String> getAllPackagesWithSecondaryDexFiles() {
@@ -852,33 +695,6 @@ public class DexManager {
return isBtmCritical;
}
- /**
- * Deletes all the optimizations files generated by ART.
- * This is best effort, and the method will log but not throw errors
- * for individual deletes
- *
- * @param packageInfo the package information.
- * @return the number of freed bytes or -1 if there was an error in the process.
- */
- public long deleteOptimizedFiles(ArtPackageInfo packageInfo)
- throws LegacyDexoptDisabledException {
- long freedBytes = 0;
- boolean hadErrors = false;
- final String packageName = packageInfo.getPackageName();
- for (String codePath : packageInfo.getCodePaths()) {
- for (String isa : packageInfo.getInstructionSets()) {
- try {
- freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
- packageInfo.getOatDir());
- } catch (InstallerException e) {
- Log.e(TAG, "Failed deleting oat files for " + codePath, e);
- hadErrors = true;
- }
- }
- }
- return hadErrors ? -1 : freedBytes;
- }
-
public static class RegisterDexModuleResult {
public RegisterDexModuleResult() {
this(false, null);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9e31748385c5..12a589264c28 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1905,6 +1905,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
accessibilityManager.performSystemAction(
AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
}
+ dismissKeyboardShortcutsMenu();
}
private void toggleNotificationPanel() {
@@ -3478,13 +3479,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
- case KeyEvent.KEYCODE_T:
- if (firstDown && event.isMetaPressed()) {
- toggleTaskbar();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_TASKBAR);
- return true;
- }
- break;
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
@@ -4735,7 +4729,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (down) {
// There may have other embedded activities on the same Task. Try to move the
// focus before processing the back event.
- mWindowManagerInternal.moveFocusToTopEmbeddedWindowIfNeeded();
+ mWindowManagerInternal.moveFocusToAdjacentEmbeddedActivityIfNeeded();
mBackKeyHandled = false;
} else {
if (!hasLongPressOnBackBehavior()) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 23f9743619e3..17e699668d14 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -11012,6 +11012,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
+ * Returns the {@link #createTime} if the top window is the `base` window. Note that do not
+ * use the window creation time because the window could be re-created when the activity
+ * relaunched if configuration changed.
+ * <p>
+ * Otherwise, return the creation time of the top window.
+ */
+ long getLastWindowCreateTime() {
+ final WindowState window = getWindow(win -> true);
+ return window != null && window.mAttrs.type != TYPE_BASE_APPLICATION
+ ? window.getCreateTime()
+ : createTime;
+ }
+
+ /**
* Adjust the source rect hint in {@link #pictureInPictureArgs} by window bounds since
* it is relative to its root view (see also b/235599028).
* It is caller's responsibility to make sure this is called exactly once when we update
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e3ac35ca8f3b..48d78f5e497b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -165,7 +165,7 @@ class BackNavigationController {
}
// Move focus to the top embedded window if possible
- if (mWindowManagerService.moveFocusToTopEmbeddedWindow(window)) {
+ if (mWindowManagerService.moveFocusToAdjacentEmbeddedWindow(window)) {
window = wmService.getFocusedWindowLocked();
if (window == null) {
Slog.e(TAG, "New focused window is null, returning null.");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 55dc30cc37d5..4c282bd1cb65 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1274,7 +1274,8 @@ class Task extends TaskFragment {
if (!isLeafTaskFragment()) {
final ActivityRecord top = topRunningActivity();
final ActivityRecord resumedActivity = getResumedActivity();
- if (resumedActivity != null && top.getTaskFragment() != this) {
+ if (resumedActivity != null
+ && (top.getTaskFragment() != this || !canBeResumed(resuming))) {
// Pausing the resumed activity because it is occluded by other task fragment.
if (startPausing(false /* uiSleeping*/, resuming, reason)) {
someActivityPaused[0]++;
@@ -3753,11 +3754,9 @@ class Task extends TaskFragment {
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && taskFragment.isEmbedded()) {
- taskFragment.mDimmerSurfaceBoosted = false;
final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
adjacentTf.assignLayer(t, layer++);
- adjacentTf.mDimmerSurfaceBoosted = true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3cf561c1b62f..dc0e0341ee8b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,9 +216,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
- /** {@code true} if the dimmer surface is boosted. {@code false} otherwise. */
- boolean mDimmerSurfaceBoosted;
-
/** Apply the dim layer on the embedded TaskFragment. */
static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index acc63305055b..daf8129f1683 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1068,9 +1068,9 @@ public abstract class WindowManagerInternal {
public abstract void clearBlockedApps();
/**
- * Moves the current focus to the top activity window if the top activity is embedded.
+ * Moves the current focus to the adjacent activity if it has the latest created window.
*/
- public abstract boolean moveFocusToTopEmbeddedWindowIfNeeded();
+ public abstract boolean moveFocusToAdjacentEmbeddedActivityIfNeeded();
/**
* Returns an instance of {@link ScreenCapture.ScreenshotHardwareBuffer} containing the current
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 207b1bbcea16..2934574acc03 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8700,14 +8700,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean moveFocusToTopEmbeddedWindowIfNeeded() {
+ public boolean moveFocusToAdjacentEmbeddedActivityIfNeeded() {
synchronized (mGlobalLock) {
final WindowState focusedWindow = getFocusedWindow();
if (focusedWindow == null) {
return false;
}
- if (moveFocusToTopEmbeddedWindow(focusedWindow)) {
+ if (moveFocusToAdjacentEmbeddedWindow(focusedWindow)) {
// Sync the input transactions to ensure the input focus updates as well.
syncInputTransactions(false);
return true;
@@ -9219,9 +9219,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
- * Move focus to the top embedded window if possible.
+ * Move focus to the adjacent embedded activity if the adjacent activity is more recently
+ * created or has a window more recently added.
*/
- boolean moveFocusToTopEmbeddedWindow(@NonNull WindowState focusedWindow) {
+ boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) {
final TaskFragment taskFragment = focusedWindow.getTaskFragment();
if (taskFragment == null) {
// Skip if not an Activity window.
@@ -9233,31 +9234,25 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
- if (taskFragment.mDimmerSurfaceBoosted) {
- // Skip if the TaskFragment currently has dimmer surface boosted.
+ if (!focusedWindow.mActivityRecord.isEmbedded()) {
+ // Skip if the focused activity is not embedded
return false;
}
- final ActivityRecord topActivity =
- taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
- if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
- // Skip if the focused activity is already the top-most activity on the Task.
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ final ActivityRecord adjacentTopActivity =
+ adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null;
+ if (adjacentTopActivity == null) {
return false;
}
- if (!topActivity.isEmbedded()) {
- // Skip if the top activity is not embedded
+ if (adjacentTopActivity.getLastWindowCreateTime()
+ < focusedWindow.mActivityRecord.getLastWindowCreateTime()) {
+ // Skip if the current focus activity has more recently active window.
return false;
}
- final TaskFragment topTaskFragment = topActivity.getTaskFragment();
- if (topTaskFragment.isIsolatedNav()
- && taskFragment.getAdjacentTaskFragment() == topTaskFragment) {
- // Skip if the top TaskFragment is adjacent to current focus and is set to isolated nav.
- return false;
- }
-
- moveFocusToActivity(topActivity);
+ moveFocusToActivity(adjacentTopActivity);
return !focusedWindow.isFocused();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2b337aed5b87..c0cf97d6d4ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -364,6 +364,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
private boolean mRedrawForSyncReported = true;
+ private long mCreateTime = System.currentTimeMillis();
/**
* Used to assosciate a given set of state changes sent from MSG_RESIZED
@@ -1714,6 +1715,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
: DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
}
+ long getCreateTime() {
+ return mCreateTime;
+ }
+
/**
* Returns true if, at any point, the application token associated with this window has actually
* displayed any windows. This is most useful with the "starting up" window to determine if any
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index d0df2b20721b..1f5451813dae 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -162,6 +162,10 @@
<xs:element type="usiVersion" name="usiVersion">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element type="lowBrightnessMode" name="lowBrightness">
+ <xs:attribute name="enabled" type="xs:boolean" use="optional"/>
+ <xs:annotation name="final"/>
+ </xs:element>
<!-- Maximum screen brightness setting when screen brightness capped in
Wear Bedtime mode. This must be a non-negative decimal within the range defined by
the first and the last brightness value in screenBrightnessMap. -->
@@ -172,6 +176,7 @@
<xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
+
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -216,6 +221,21 @@
</xs:restriction>
</xs:simpleType>
+ <xs:complexType name="lowBrightnessMode">
+ <xs:sequence>
+ <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
+ maxOccurs="1">
+ </xs:element>
+ <xs:element name="nits" type="xs:float" maxOccurs="unbounded">
+ </xs:element>
+ <xs:element name="backlight" type="xs:float" maxOccurs="unbounded">
+ </xs:element>
+ <xs:element name="brightness" type="xs:float" maxOccurs="unbounded">
+ </xs:element>
+ </xs:sequence>
+ <xs:attribute name="enabled" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+
<xs:complexType name="highBrightnessMode">
<xs:all>
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 00dc90828d90..c39c3d7ee7c6 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -113,6 +113,7 @@ package com.android.server.display.config {
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout();
method public final com.android.server.display.config.SensorDetails getLightSensor();
+ method public final com.android.server.display.config.LowBrightnessMode getLowBrightness();
method public com.android.server.display.config.LuxThrottling getLuxThrottling();
method @Nullable public final String getName();
method public com.android.server.display.config.PowerThrottlingConfig getPowerThrottlingConfig();
@@ -149,6 +150,7 @@ package com.android.server.display.config {
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
+ method public final void setLowBrightness(com.android.server.display.config.LowBrightnessMode);
method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
method public final void setName(@Nullable String);
method public void setPowerThrottlingConfig(com.android.server.display.config.PowerThrottlingConfig);
@@ -248,6 +250,17 @@ package com.android.server.display.config {
method public java.util.List<java.math.BigInteger> getItem();
}
+ public class LowBrightnessMode {
+ ctor public LowBrightnessMode();
+ method public java.util.List<java.lang.Float> getBacklight();
+ method public java.util.List<java.lang.Float> getBrightness();
+ method public boolean getEnabled();
+ method public java.util.List<java.lang.Float> getNits();
+ method public java.math.BigDecimal getTransitionPoint();
+ method public void setEnabled(boolean);
+ method public void setTransitionPoint(java.math.BigDecimal);
+ }
+
public class LuxThrottling {
ctor public LuxThrottling();
method @NonNull public final java.util.List<com.android.server.display.config.BrightnessLimitMap> getBrightnessLimitMap();
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 8dfa685bf6ff..da965bb02460 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -24,5 +24,6 @@ java_library_static {
"app-compat-annotations",
"service-permission.stubs.system_server",
"device_policy_aconfig_flags_lib",
+ "androidx.annotation_annotation",
],
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cebd6d05e9ac..f955b91136a3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9523,7 +9523,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (setProfileOwnerOnCurrentUserIfNecessary
&& mInjector.userManagerIsHeadlessSystemUserMode()
- && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
+ && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
int currentForegroundUser;
synchronized (getLockObject()) {
currentForegroundUser = getCurrentForegroundUserId();
@@ -9539,7 +9540,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
- private int getHeadlessDeviceOwnerMode() {
+ private int getHeadlessDeviceOwnerModeForDeviceOwner() {
synchronized (getLockObject()) {
ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
if (deviceOwner == null) {
@@ -9549,6 +9550,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private int getHeadlessDeviceOwnerModeForDeviceAdmin(
+ @Nullable ComponentName deviceAdmin, int userId) {
+ synchronized (getLockObject()) {
+ if (deviceAdmin == null) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ DeviceAdminInfo adminInfo = findAdmin(
+ deviceAdmin, userId, /* throwForMissingPermission= */ false);
+ if (adminInfo == null) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ return adminInfo.getHeadlessDeviceOwnerMode();
+ }
+ }
+
/**
* This API is cached: invalidate with invalidateBinderCaches().
*/
@@ -12308,7 +12324,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (Flags.headlessDeviceOwnerSingleUserEnabled()) {
// Block this method if the device is in headless main user mode
Preconditions.checkCallAuthorization(
- getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+ getHeadlessDeviceOwnerModeForDeviceOwner()
+ != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
"createAndManageUser was called while in headless single user mode");
}
@@ -16746,8 +16763,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- private int checkProvisioningPreconditionSkipPermission(String action,
- String packageName, int userId) {
+
+ private int checkProvisioningPreconditionSkipPermission(
+ String action, String packageName, int userId) {
+ return checkProvisioningPreconditionSkipPermission(
+ action, packageName, /* componentName = */ null, userId);
+ }
+
+ private int checkProvisioningPreconditionSkipPermission(
+ String action, ComponentName componentName, int userId) {
+ return checkProvisioningPreconditionSkipPermission(
+ action, componentName.getPackageName(), componentName, userId);
+ }
+
+ private int checkProvisioningPreconditionSkipPermission(
+ String action, String packageName, @Nullable ComponentName componentName, int userId) {
if (!mHasFeature) {
logMissingFeatureAction("Cannot check provisioning for action " + action);
return STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
@@ -16756,11 +16786,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
}
final int code = checkProvisioningPreConditionSkipPermissionNoLog(
- action, packageName, userId);
+ action, packageName, componentName, userId);
if (code != STATUS_OK) {
Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName
- + ") failed: "
- + computeProvisioningErrorString(code, mInjector.userHandleGetCallingUserId()));
+ + ") failed: " + computeProvisioningErrorString(
+ code, mInjector.userHandleGetCallingUserId()));
}
return code;
}
@@ -16783,14 +16813,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private int checkProvisioningPreConditionSkipPermissionNoLog(String action,
- String packageName, int userId) {
+ String packageName, @Nullable ComponentName componentName, int userId) {
+ if (packageName != null && componentName != null
+ && !packageName.equals(componentName.getPackageName())) {
+ throw new IllegalArgumentException("PackageName: " + packageName + " is not the same as"
+ + " the package provided in componentName: " + componentName);
+ }
if (action != null) {
switch (action) {
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, userId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
- return checkDeviceOwnerProvisioningPreCondition(userId);
+ return checkDeviceOwnerProvisioningPreCondition(componentName, userId);
}
}
throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -16825,16 +16860,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int ensureSetUpUser = UserHandle.USER_SYSTEM;
if (isHeadlessSystemUserMode) {
if (owner != null) {
- adminInfo = findAdmin(owner,
- deviceOwnerUserId, /* throwForMissingPermission= */ false);
+ int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
+ owner, deviceOwnerUserId);
isHeadlessModeAffiliated =
- adminInfo.getHeadlessDeviceOwnerMode()
- == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+ headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
isHeadlessModeSingleUser =
- adminInfo.getHeadlessDeviceOwnerMode()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+ headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
if (!isHeadlessModeAffiliated && !isHeadlessModeSingleUser) {
return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
@@ -16880,7 +16913,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
return STATUS_OK;
} else {
- // DO has to be user 0
+ // DO has to be user 0 if setting affiliated DO
if ((!isHeadlessSystemUserMode || isHeadlessModeAffiliated)
&& deviceOwnerUserId != UserHandle.USER_SYSTEM) {
return STATUS_NOT_SYSTEM_USER;
@@ -16904,17 +16937,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.count() > allowedUsers;
}
- private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
+ private int checkDeviceOwnerProvisioningPreCondition(
+ @Nullable ComponentName componentName, @UserIdInt int callingUserId) {
synchronized (getLockObject()) {
- final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
- && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
- || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
- ? UserHandle.USER_SYSTEM
- : callingUserId;
+ int deviceOwnerUserId = -1;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && getHeadlessDeviceOwnerModeForDeviceAdmin(componentName, callingUserId)
+ == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED
+ ? UserHandle.USER_SYSTEM : callingUserId;
+ } else {
+ deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED
+ ? UserHandle.USER_SYSTEM : callingUserId;
+ }
Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
callingUserId, deviceOwnerUserId);
// hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb.
- return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null,
+ return checkDeviceOwnerProvisioningPreConditionLocked(componentName,
deviceOwnerUserId, callingUserId, /* isAdb= */ false,
/* hasIncompatibleAccountsOrNonAdb=*/ true);
}
@@ -21082,7 +21123,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
final int result = checkProvisioningPreconditionSkipPermission(
- ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName(), caller.getUserId());
+ ACTION_PROVISION_MANAGED_PROFILE, admin, caller.getUserId());
if (result != STATUS_OK) {
throw new ServiceSpecificException(
ERROR_PRE_CONDITION_FAILED,
@@ -21568,8 +21609,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
int result = checkProvisioningPreconditionSkipPermission(
- ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName(),
- caller.getUserId());
+ ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin, caller.getUserId());
if (result != STATUS_OK) {
throw new ServiceSpecificException(
ERROR_PRE_CONDITION_FAILED,
@@ -21581,17 +21621,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
-
-
boolean isSingleUserMode;
if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
- DeviceAdminInfo adminInfo = findAdmin(
- deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
- isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
- == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
+ deviceAdmin, caller.getUserId());
+ isSingleUserMode =
+ headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
} else {
isSingleUserMode =
- (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ getHeadlessDeviceOwnerModeForDeviceOwner()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
}
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
&& isSingleUserMode
@@ -21606,7 +21645,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"PackageManager failed to remove non required apps.");
}
-
if (!setActiveAdminAndDeviceOwner(deviceOwnerUserId, deviceAdmin)) {
throw new ServiceSpecificException(
ERROR_SET_DEVICE_OWNER_FAILED, "Failed to set device owner.");
@@ -24410,6 +24448,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
caller.getUserId());
- return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+ return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerModeForDeviceOwner());
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index f3b164c6501c..94c137444ede 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -25,15 +25,16 @@ import static android.app.admin.DevicePolicyManager.REQUIRED_APP_MANAGED_USER;
import static android.content.pm.PackageManager.GET_META_DATA;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.devicepolicy.DevicePolicyManagerService.dumpResources;
+import static com.android.server.devicepolicy.DevicePolicyManagerService.dumpApps;
import static java.util.Objects.requireNonNull;
+import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
@@ -67,13 +68,16 @@ public class OverlayPackagesProvider {
protected static final String TAG = "OverlayPackagesProvider";
private static final Map<String, String> sActionToMetadataKeyMap = new HashMap<>();
- {
+
+ static {
sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_USER, REQUIRED_APP_MANAGED_USER);
sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_PROFILE, REQUIRED_APP_MANAGED_PROFILE);
sActionToMetadataKeyMap.put(ACTION_PROVISION_MANAGED_DEVICE, REQUIRED_APP_MANAGED_DEVICE);
}
+
private static final Set<String> sAllowedActions = new HashSet<>();
- {
+
+ static {
sAllowedActions.add(ACTION_PROVISION_MANAGED_USER);
sAllowedActions.add(ACTION_PROVISION_MANAGED_PROFILE);
sAllowedActions.add(ACTION_PROVISION_MANAGED_DEVICE);
@@ -83,8 +87,13 @@ public class OverlayPackagesProvider {
private final Context mContext;
private final Injector mInjector;
+ private final RecursiveStringArrayResourceResolver mRecursiveStringArrayResourceResolver;
+
public OverlayPackagesProvider(Context context) {
- this(context, new DefaultInjector());
+ this(
+ context,
+ new DefaultInjector(),
+ new RecursiveStringArrayResourceResolver(context.getResources()));
}
@VisibleForTesting
@@ -113,8 +122,8 @@ public class OverlayPackagesProvider {
public String getDevicePolicyManagementRoleHolderPackageName(Context context) {
return Binder.withCleanCallingIdentity(() -> {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+ List<String> roleHolders = roleManager.getRoleHolders(
+ RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
if (roleHolders.isEmpty()) {
return null;
}
@@ -124,17 +133,20 @@ public class OverlayPackagesProvider {
}
@VisibleForTesting
- OverlayPackagesProvider(Context context, Injector injector) {
+ OverlayPackagesProvider(Context context, Injector injector,
+ RecursiveStringArrayResourceResolver recursiveStringArrayResourceResolver) {
mContext = context;
- mPm = checkNotNull(context.getPackageManager());
- mInjector = checkNotNull(injector);
+ mPm = requireNonNull(context.getPackageManager());
+ mInjector = requireNonNull(injector);
+ mRecursiveStringArrayResourceResolver = requireNonNull(
+ recursiveStringArrayResourceResolver);
}
/**
* Computes non-required apps. All the system apps with a launcher that are not in
* the required set of packages, and all mainline modules that are not declared as required
* via metadata in their manifests, will be considered as non-required apps.
- *
+ * <p>
* Note: If an app is mistakenly listed as both required and disallowed, it will be treated as
* disallowed.
*
@@ -176,12 +188,12 @@ public class OverlayPackagesProvider {
/**
* Returns a subset of {@code packageNames} whose packages are mainline modules declared as
* required apps via their app metadata.
+ *
* @see DevicePolicyManager#REQUIRED_APP_MANAGED_USER
* @see DevicePolicyManager#REQUIRED_APP_MANAGED_DEVICE
* @see DevicePolicyManager#REQUIRED_APP_MANAGED_PROFILE
*/
- private Set<String> getRequiredAppsMainlineModules(
- Set<String> packageNames,
+ private Set<String> getRequiredAppsMainlineModules(Set<String> packageNames,
String provisioningAction) {
final Set<String> result = new HashSet<>();
for (String packageName : packageNames) {
@@ -225,8 +237,8 @@ public class OverlayPackagesProvider {
}
private boolean isApkInApexMainlineModule(String packageName) {
- final String apexPackageName =
- mInjector.getActiveApexPackageNameContainingPackage(packageName);
+ final String apexPackageName = mInjector.getActiveApexPackageNameContainingPackage(
+ packageName);
return apexPackageName != null;
}
@@ -274,112 +286,94 @@ public class OverlayPackagesProvider {
}
private Set<String> getRequiredAppsSet(String provisioningAction) {
- final int resId;
- switch (provisioningAction) {
- case ACTION_PROVISION_MANAGED_USER:
- resId = R.array.required_apps_managed_user;
- break;
- case ACTION_PROVISION_MANAGED_PROFILE:
- resId = R.array.required_apps_managed_profile;
- break;
- case ACTION_PROVISION_MANAGED_DEVICE:
- resId = R.array.required_apps_managed_device;
- break;
- default:
- throw new IllegalArgumentException("Provisioning type "
- + provisioningAction + " not supported.");
- }
- return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
+ final int resId = switch (provisioningAction) {
+ case ACTION_PROVISION_MANAGED_USER -> R.array.required_apps_managed_user;
+ case ACTION_PROVISION_MANAGED_PROFILE -> R.array.required_apps_managed_profile;
+ case ACTION_PROVISION_MANAGED_DEVICE -> R.array.required_apps_managed_device;
+ default -> throw new IllegalArgumentException(
+ "Provisioning type " + provisioningAction + " not supported.");
+ };
+ return resolveStringArray(resId);
}
private Set<String> getDisallowedAppsSet(String provisioningAction) {
- final int resId;
- switch (provisioningAction) {
- case ACTION_PROVISION_MANAGED_USER:
- resId = R.array.disallowed_apps_managed_user;
- break;
- case ACTION_PROVISION_MANAGED_PROFILE:
- resId = R.array.disallowed_apps_managed_profile;
- break;
- case ACTION_PROVISION_MANAGED_DEVICE:
- resId = R.array.disallowed_apps_managed_device;
- break;
- default:
- throw new IllegalArgumentException("Provisioning type "
- + provisioningAction + " not supported.");
- }
- return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
+ final int resId = switch (provisioningAction) {
+ case ACTION_PROVISION_MANAGED_USER -> R.array.disallowed_apps_managed_user;
+ case ACTION_PROVISION_MANAGED_PROFILE -> R.array.disallowed_apps_managed_profile;
+ case ACTION_PROVISION_MANAGED_DEVICE -> R.array.disallowed_apps_managed_device;
+ default -> throw new IllegalArgumentException(
+ "Provisioning type " + provisioningAction + " not supported.");
+ };
+ return resolveStringArray(resId);
}
private Set<String> getVendorRequiredAppsSet(String provisioningAction) {
- final int resId;
- switch (provisioningAction) {
- case ACTION_PROVISION_MANAGED_USER:
- resId = R.array.vendor_required_apps_managed_user;
- break;
- case ACTION_PROVISION_MANAGED_PROFILE:
- resId = R.array.vendor_required_apps_managed_profile;
- break;
- case ACTION_PROVISION_MANAGED_DEVICE:
- resId = R.array.vendor_required_apps_managed_device;
- break;
- default:
- throw new IllegalArgumentException("Provisioning type "
- + provisioningAction + " not supported.");
- }
- return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
+ final int resId = switch (provisioningAction) {
+ case ACTION_PROVISION_MANAGED_USER -> R.array.vendor_required_apps_managed_user;
+ case ACTION_PROVISION_MANAGED_PROFILE -> R.array.vendor_required_apps_managed_profile;
+ case ACTION_PROVISION_MANAGED_DEVICE -> R.array.vendor_required_apps_managed_device;
+ default -> throw new IllegalArgumentException(
+ "Provisioning type " + provisioningAction + " not supported.");
+ };
+ return resolveStringArray(resId);
}
private Set<String> getVendorDisallowedAppsSet(String provisioningAction) {
- final int resId;
- switch (provisioningAction) {
- case ACTION_PROVISION_MANAGED_USER:
- resId = R.array.vendor_disallowed_apps_managed_user;
- break;
- case ACTION_PROVISION_MANAGED_PROFILE:
- resId = R.array.vendor_disallowed_apps_managed_profile;
- break;
- case ACTION_PROVISION_MANAGED_DEVICE:
- resId = R.array.vendor_disallowed_apps_managed_device;
- break;
- default:
- throw new IllegalArgumentException("Provisioning type "
- + provisioningAction + " not supported.");
+ final int resId = switch (provisioningAction) {
+ case ACTION_PROVISION_MANAGED_USER -> R.array.vendor_disallowed_apps_managed_user;
+ case ACTION_PROVISION_MANAGED_PROFILE -> R.array.vendor_disallowed_apps_managed_profile;
+ case ACTION_PROVISION_MANAGED_DEVICE -> R.array.vendor_disallowed_apps_managed_device;
+ default -> throw new IllegalArgumentException(
+ "Provisioning type " + provisioningAction + " not supported.");
+ };
+ return resolveStringArray(resId);
+ }
+
+ private Set<String> resolveStringArray(@ArrayRes int resId) {
+ if (Flags.isRecursiveRequiredAppMergingEnabled()) {
+ return mRecursiveStringArrayResourceResolver.resolve(mContext.getPackageName(), resId);
+ } else {
+ return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
}
- return new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(resId)));
}
void dump(IndentingPrintWriter pw) {
pw.println("OverlayPackagesProvider");
pw.increaseIndent();
- dumpResources(pw, mContext, "required_apps_managed_device",
- R.array.required_apps_managed_device);
- dumpResources(pw, mContext, "required_apps_managed_user",
- R.array.required_apps_managed_user);
- dumpResources(pw, mContext, "required_apps_managed_profile",
- R.array.required_apps_managed_profile);
-
- dumpResources(pw, mContext, "disallowed_apps_managed_device",
- R.array.disallowed_apps_managed_device);
- dumpResources(pw, mContext, "disallowed_apps_managed_user",
- R.array.disallowed_apps_managed_user);
- dumpResources(pw, mContext, "disallowed_apps_managed_device",
- R.array.disallowed_apps_managed_device);
-
- dumpResources(pw, mContext, "vendor_required_apps_managed_device",
- R.array.vendor_required_apps_managed_device);
- dumpResources(pw, mContext, "vendor_required_apps_managed_user",
- R.array.vendor_required_apps_managed_user);
- dumpResources(pw, mContext, "vendor_required_apps_managed_profile",
- R.array.vendor_required_apps_managed_profile);
-
- dumpResources(pw, mContext, "vendor_disallowed_apps_managed_user",
- R.array.vendor_disallowed_apps_managed_user);
- dumpResources(pw, mContext, "vendor_disallowed_apps_managed_device",
- R.array.vendor_disallowed_apps_managed_device);
- dumpResources(pw, mContext, "vendor_disallowed_apps_managed_profile",
- R.array.vendor_disallowed_apps_managed_profile);
+ dumpApps(pw, "required_apps_managed_device",
+ resolveStringArray(R.array.required_apps_managed_device).toArray(String[]::new));
+ dumpApps(pw, "required_apps_managed_user",
+ resolveStringArray(R.array.required_apps_managed_user).toArray(String[]::new));
+ dumpApps(pw, "required_apps_managed_profile",
+ resolveStringArray(R.array.required_apps_managed_profile).toArray(String[]::new));
+
+ dumpApps(pw, "disallowed_apps_managed_device",
+ resolveStringArray(R.array.disallowed_apps_managed_device).toArray(String[]::new));
+ dumpApps(pw, "disallowed_apps_managed_user",
+ resolveStringArray(R.array.disallowed_apps_managed_user).toArray(String[]::new));
+ dumpApps(pw, "disallowed_apps_managed_device",
+ resolveStringArray(R.array.disallowed_apps_managed_device).toArray(String[]::new));
+
+ dumpApps(pw, "vendor_required_apps_managed_device",
+ resolveStringArray(R.array.vendor_required_apps_managed_device).toArray(
+ String[]::new));
+ dumpApps(pw, "vendor_required_apps_managed_user",
+ resolveStringArray(R.array.vendor_required_apps_managed_user).toArray(
+ String[]::new));
+ dumpApps(pw, "vendor_required_apps_managed_profile",
+ resolveStringArray(R.array.vendor_required_apps_managed_profile).toArray(
+ String[]::new));
+
+ dumpApps(pw, "vendor_disallowed_apps_managed_user",
+ resolveStringArray(R.array.vendor_disallowed_apps_managed_user).toArray(
+ String[]::new));
+ dumpApps(pw, "vendor_disallowed_apps_managed_device",
+ resolveStringArray(R.array.vendor_disallowed_apps_managed_device).toArray(
+ String[]::new));
+ dumpApps(pw, "vendor_disallowed_apps_managed_profile",
+ resolveStringArray(R.array.vendor_disallowed_apps_managed_profile).toArray(
+ String[]::new));
pw.decreaseIndent();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RecursiveStringArrayResourceResolver.java b/services/devicepolicy/java/com/android/server/devicepolicy/RecursiveStringArrayResourceResolver.java
new file mode 100644
index 000000000000..935e051b64ea
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RecursiveStringArrayResourceResolver.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 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.devicepolicy;
+
+import android.annotation.SuppressLint;
+import android.content.res.Resources;
+
+import androidx.annotation.ArrayRes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class encapsulating all the logic for recursive string-array resource resolution.
+ */
+public class RecursiveStringArrayResourceResolver {
+ private static final String IMPORT_PREFIX = "#import:";
+ private static final String SEPARATOR = "/";
+ private static final String PWP = ".";
+
+ private final Resources mResources;
+
+ /**
+ * @param resources Android resource access object to use when resolving resources
+ */
+ public RecursiveStringArrayResourceResolver(Resources resources) {
+ this.mResources = resources;
+ }
+
+ /**
+ * Resolves a given {@code <string-array/>} resource specified via
+ * {@param rootId} in {@param pkg}. During resolution all values prefixed with
+ * {@link #IMPORT_PREFIX} are expanded and injected
+ * into the final list at the position of the import statement,
+ * pushing all the following values (and their expansions) down.
+ * Circular imports are tracked and skipped to avoid infinite resolution loops without losing
+ * data.
+ *
+ * <p>
+ * The import statements are expected in a form of
+ * "{@link #IMPORT_PREFIX}{package}{@link #SEPARATOR}{resourceName}"
+ * If the resource being imported is from the same package, its package can be specified as a
+ * {@link #PWP} shorthand `.`
+ * > e.g.:
+ * > {@code "#import:com.android.internal/disallowed_apps_managed_user"}
+ * > {@code "#import:./disallowed_apps_managed_user"}
+ *
+ * <p>
+ * Any incorrect or unresolvable import statement
+ * will cause the entire resolution to fail with an error.
+ *
+ * @param pkg the package owning the resource
+ * @param rootId the id of the {@code <string-array>} resource within {@param pkg} to start the
+ * resolution from
+ * @return a flattened list of all the resolved string array values from the root resource
+ * as well as all the imported arrays
+ */
+ public Set<String> resolve(String pkg, @ArrayRes int rootId) {
+ return resolve(List.of(), pkg, rootId);
+ }
+
+ /**
+ * A version of resolve that tracks already imported resources
+ * to avoid circular imports and wasted work.
+ *
+ * @param cache a list of already resolved packages to be skipped for further resolution
+ */
+ private Set<String> resolve(Collection<String> cache, String pkg, @ArrayRes int rootId) {
+ final var strings = mResources.getStringArray(rootId);
+ final var runningCache = new ArrayList<>(cache);
+
+ final var result = new HashSet<String>();
+ for (var string : strings) {
+ final String ref;
+ if (string.startsWith(IMPORT_PREFIX)) {
+ ref = string.substring(IMPORT_PREFIX.length());
+ } else {
+ ref = null;
+ }
+
+ if (ref == null) {
+ result.add(string);
+ } else if (!runningCache.contains(ref)) {
+ final var next = resolveImport(runningCache, pkg, ref);
+ runningCache.addAll(next);
+ result.addAll(next);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Resolves an import of the {@code <string-array>} resource
+ * in the context of {@param importingPackage} by the provided {@param ref}.
+ *
+ * @param cache a list of already resolved packages to be passed along into chained
+ * {@link #resolve} calls
+ * @param importingPackage the package that owns the resource which defined the import being
+ * processed.
+ * It is also used to expand all {@link #PWP} shorthands in
+ * {@param ref}
+ * @param ref reference to the resource to be imported in a form of
+ * "{package}{@link #SEPARATOR}{resourceName}".
+ * e.g.: {@code com.android.internal/disallowed_apps_managed_user}
+ */
+ private Set<String> resolveImport(
+ Collection<String> cache,
+ String importingPackage,
+ String ref) {
+ final var chunks = ref.split(SEPARATOR, 2);
+ final var pkg = chunks[0];
+ final var name = chunks[1];
+ final String resolvedPkg;
+ if (Objects.equals(pkg, PWP)) {
+ resolvedPkg = importingPackage;
+ } else {
+ resolvedPkg = pkg;
+ }
+ @SuppressLint("DiscouragedApi") final var importId = mResources.getIdentifier(
+ /* name = */ name,
+ /* defType = */ "array",
+ /* defPackage = */ resolvedPkg);
+ if (importId == 0) {
+ throw new Resources.NotFoundException(
+ /* name= */ String.format("%s:array/%s", resolvedPkg, name));
+ }
+ return resolve(cache, resolvedPkg, importId);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index b0f7bfa33415..54de64e2f3a8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
@@ -96,6 +97,8 @@ public class AutomaticBrightnessControllerTest {
@Mock HysteresisLevels mScreenBrightnessThresholdsIdle;
@Mock Handler mNoOpHandler;
@Mock BrightnessRangeController mBrightnessRangeController;
+ @Mock
+ BrightnessClamperController mBrightnessClamperController;
@Mock BrightnessThrottler mBrightnessThrottler;
@Before
@@ -161,7 +164,8 @@ public class AutomaticBrightnessControllerTest {
mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle,
mContext, mBrightnessRangeController, mBrightnessThrottler,
useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1,
- useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits
+ useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userNits,
+ mBrightnessClamperController
);
when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 35b69f812ff0..73a2f655da8d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -44,6 +44,7 @@ import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
import android.os.Temperature;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.provider.Settings;
import android.util.SparseArray;
import android.util.Spline;
@@ -57,6 +58,7 @@ import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.display.feature.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -380,7 +382,7 @@ public final class DisplayDeviceConfigTest {
public void testInvalidLuxThrottling() throws Exception {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getInvalidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true));
+ /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
mDisplayDeviceConfig.getLuxThrottlingData();
@@ -588,7 +590,7 @@ public final class DisplayDeviceConfigTest {
public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues(),
- /* includeIdleMode= */ true));
+ /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
assertNull(mDisplayDeviceConfig.getProximitySensor());
}
@@ -596,7 +598,7 @@ public final class DisplayDeviceConfigTest {
public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(
getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
- /* includeIdleMode= */ true));
+ /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
assertEquals("test_proximity_sensor",
mDisplayDeviceConfig.getProximitySensor().type);
assertEquals("Test Proximity Sensor",
@@ -784,7 +786,7 @@ public final class DisplayDeviceConfigTest {
@Test
public void testBrightnessRamps_IdleFallsBackToConfigInteractive() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
@@ -801,14 +803,14 @@ public final class DisplayDeviceConfigTest {
@Test
public void testBrightnessCapForWearBedtimeMode() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
}
@Test
public void testAutoBrightnessBrighteningLevels() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
assertArrayEquals(new float[]{0.0f, 80},
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
@@ -871,7 +873,7 @@ public final class DisplayDeviceConfigTest {
when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
setupDisplayDeviceConfigFromConfigResourceFile();
setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
- getValidProxSensor(), /* includeIdleMode= */ false));
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)},
@@ -904,6 +906,18 @@ public final class DisplayDeviceConfigTest {
assertFalse(mDisplayDeviceConfig.isAutoBrightnessAvailable());
}
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ @Test
+ public void testEvenDimmer() throws IOException {
+ when(mFlags.isEvenDimmerEnabled()).thenReturn(true);
+ setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
+ getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
+
+ assertTrue(mDisplayDeviceConfig.getLbmEnabled());
+ assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA);
+ assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1229,11 +1243,11 @@ public final class DisplayDeviceConfigTest {
private String getContent() {
return getContent(getValidLuxThrottling(), getValidProxSensor(),
- /* includeIdleMode= */ true);
+ /* includeIdleMode= */ true, false);
}
private String getContent(String brightnessCapConfig, String proxSensor,
- boolean includeIdleMode) {
+ boolean includeIdleMode, boolean enableEvenDimmer) {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<displayConfiguration>\n"
+ "<name>Example Display</name>\n"
@@ -1603,6 +1617,7 @@ public final class DisplayDeviceConfigTest {
+ "<majorVersion>2</majorVersion>\n"
+ "<minorVersion>0</minorVersion>\n"
+ "</usiVersion>\n"
+ + evenDimmerConfig(enableEvenDimmer)
+ "<screenBrightnessCapForWearBedtimeMode>"
+ "0.1"
+ "</screenBrightnessCapForWearBedtimeMode>"
@@ -1621,6 +1636,24 @@ public final class DisplayDeviceConfigTest {
+ "</displayConfiguration>\n";
}
+ private String evenDimmerConfig(boolean enabled) {
+ return (enabled ? "<lowBrightness enabled=\"true\">" : "<lowBrightness enabled=\"false\">")
+ + " <transitionPoint>0.1</transitionPoint>\n"
+ + " <nits>0.2</nits>\n"
+ + " <nits>2.0</nits>\n"
+ + " <nits>500.0</nits>\n"
+ + " <nits>1000.0</nits>\n"
+ + " <backlight>0</backlight>\n"
+ + " <backlight>0.0001</backlight>\n"
+ + " <backlight>0.5</backlight>\n"
+ + " <backlight>1.0</backlight>\n"
+ + " <brightness>0</brightness>\n"
+ + " <brightness>0.1</brightness>\n"
+ + " <brightness>0.5</brightness>\n"
+ + " <brightness>1.0</brightness>\n"
+ + "</lowBrightness>";
+ }
+
private void mockDeviceConfigs() {
when(mResources.getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 01598aeba8fe..740ffc90d785 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1184,7 +1184,8 @@ public final class DisplayPowerControllerTest {
/* ambientLightHorizonShort= */ anyInt(),
/* ambientLightHorizonLong= */ anyInt(),
eq(lux),
- eq(nits)
+ eq(nits),
+ any(BrightnessClamperController.class)
);
}
@@ -2121,7 +2122,8 @@ public final class DisplayPowerControllerTest {
HysteresisLevels screenBrightnessThresholdsIdle, Context context,
BrightnessRangeController brightnessRangeController,
BrightnessThrottler brightnessThrottler, int ambientLightHorizonShort,
- int ambientLightHorizonLong, float userLux, float userNits) {
+ int ambientLightHorizonLong, float userLux, float userNits,
+ BrightnessClamperController brightnessClamperController) {
return mAutomaticBrightnessController;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index ac7d1f5ba452..e4a7d982514f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -65,7 +65,7 @@ class BrightnessLowLuxModifierTest {
Settings.Secure.putIntForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.7f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 30.0f, userId)
modifier.recalculateLowerBound()
testHandler.flush()
assertThat(modifier.isActive).isTrue()
@@ -81,11 +81,22 @@ class BrightnessLowLuxModifierTest {
Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
Settings.Secure.putFloatForUser(context.contentResolver,
Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.0f, userId)
- modifier.recalculateLowerBound()
+ modifier.onAmbientLuxChange(3000.0f)
testHandler.flush()
assertThat(modifier.isActive).isTrue()
// Test restriction from lux setting
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
}
+
+ @Test
+ fun testSettingOffDisablesModifier() {
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
+ modifier.onAmbientLuxChange(3000.0f)
+ testHandler.flush()
+ assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
deleted file mode 100644
index 9a7ee4d7887b..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED;
-import static com.android.server.pm.BackgroundDexOptService.STATUS_FATAL_ERROR;
-import static com.android.server.pm.BackgroundDexOptService.STATUS_OK;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.annotation.Nullable;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobScheduler;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.dex.DexoptOptions;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.stream.Collectors;
-
-@RunWith(MockitoJUnitRunner.class)
-public final class BackgroundDexOptServiceUnitTest {
- private static final String TAG = BackgroundDexOptServiceUnitTest.class.getSimpleName();
-
- private static final long USABLE_SPACE_NORMAL = 1_000_000_000;
- private static final long STORAGE_LOW_BYTES = 1_000_000;
-
- private static final long TEST_WAIT_TIMEOUT_MS = 10_000;
-
- private static final String PACKAGE_AAA = "aaa";
- private static final List<String> DEFAULT_PACKAGE_LIST = List.of(PACKAGE_AAA, "bbb");
- private int mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
-
- // Store expected dexopt sequence for verification.
- private ArrayList<DexOptInfo> mDexInfoSequence = new ArrayList<>();
-
- @Mock
- private Context mContext;
- @Mock
- private PackageManagerService mPackageManager;
- @Mock
- private DexOptHelper mDexOptHelper;
- @Mock
- private DexManager mDexManager;
- @Mock
- private PinnerService mPinnerService;
- @Mock
- private JobScheduler mJobScheduler;
- @Mock
- private BackgroundDexOptService.Injector mInjector;
- @Mock
- private BackgroundDexOptJobService mJobServiceForPostBoot;
- @Mock
- private BackgroundDexOptJobService mJobServiceForIdle;
-
- private final JobParameters mJobParametersForPostBoot =
- createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
- private final JobParameters mJobParametersForIdle =
- createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
-
- private static JobParameters createJobParameters(int jobId) {
- JobParameters params = mock(JobParameters.class);
- when(params.getJobId()).thenReturn(jobId);
- return params;
- }
-
- private BackgroundDexOptService mService;
-
- private StartAndWaitThread mDexOptThread;
- private StartAndWaitThread mCancelThread;
-
- @Before
- public void setUp() throws Exception {
- // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run
- // when ART Service is enabled.
- Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false));
-
- when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID);
- when(mInjector.getContext()).thenReturn(mContext);
- when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper);
- when(mInjector.getDexManager()).thenReturn(mDexManager);
- when(mInjector.getPinnerService()).thenReturn(mPinnerService);
- when(mInjector.getJobScheduler()).thenReturn(mJobScheduler);
- when(mInjector.getPackageManagerService()).thenReturn(mPackageManager);
-
- // These mocking can be overwritten in some tests but still keep it here as alternative
- // takes too many repetitive codes.
- when(mInjector.getDataDirUsableSpace()).thenReturn(USABLE_SPACE_NORMAL);
- when(mInjector.getDataDirStorageLowBytes()).thenReturn(STORAGE_LOW_BYTES);
- when(mInjector.getDexOptThermalCutoff()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL);
- when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_NONE);
- when(mInjector.supportSecondaryDex()).thenReturn(true);
- setupDexOptHelper();
-
- mService = new BackgroundDexOptService(mInjector);
- }
-
- private void setupDexOptHelper() {
- when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST);
- when(mDexOptHelper.performDexOptWithStatus(any())).thenAnswer(inv -> {
- DexoptOptions opt = inv.getArgument(0);
- if (opt.getPackageName().equals(PACKAGE_AAA)) {
- return mDexOptResultForPackageAAA;
- }
- return PackageDexOptimizer.DEX_OPT_PERFORMED;
- });
- when(mDexOptHelper.performDexOpt(any())).thenReturn(true);
- }
-
- @After
- public void tearDown() throws Exception {
- LocalServices.removeServiceForTest(BackgroundDexOptService.class);
- }
-
- @Test
- public void testGetService() {
- assertThat(BackgroundDexOptService.getService()).isEqualTo(mService);
- }
-
- @Test
- public void testBootCompleted() throws Exception {
- initUntilBootCompleted();
- }
-
- @Test
- public void testNoExecutionForIdleJobBeforePostBootUpdate() throws Exception {
- initUntilBootCompleted();
-
- assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
- }
-
- @Test
- public void testNoExecutionForLowStorage() throws Exception {
- initUntilBootCompleted();
- when(mPackageManager.isStorageLow()).thenReturn(true);
-
- assertThat(mService.onStartJob(mJobServiceForPostBoot,
- mJobParametersForPostBoot)).isFalse();
- verify(mDexOptHelper, never()).performDexOpt(any());
- }
-
- @Test
- public void testNoExecutionForNoOptimizablePackages() throws Exception {
- initUntilBootCompleted();
- when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList());
-
- assertThat(mService.onStartJob(mJobServiceForPostBoot,
- mJobParametersForPostBoot)).isFalse();
- verify(mDexOptHelper, never()).performDexOpt(any());
- }
-
- @Test
- public void testPostBootUpdateFullRun() throws Exception {
- initUntilBootCompleted();
-
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
- }
-
- @Test
- public void testPostBootUpdateFullRunWithPackageFailure() throws Exception {
- mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
-
- initUntilBootCompleted();
-
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
- assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
- assertThat(getFailedPackageNamesSecondary()).isEmpty();
- }
-
- @Test
- public void testIdleJobFullRun() throws Exception {
- initUntilBootCompleted();
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
- runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
- }
-
- @Test
- public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() throws Exception {
- mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED;
-
- initUntilBootCompleted();
-
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
- assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
- assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
- runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
-
- assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
- assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
- mService.notifyPackageChanged(PACKAGE_AAA);
-
- assertThat(getFailedPackageNamesPrimary()).isEmpty();
- assertThat(getFailedPackageNamesSecondary()).isEmpty();
-
- // Succeed this time.
- mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
-
- runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null);
-
- assertThat(getFailedPackageNamesPrimary()).isEmpty();
- assertThat(getFailedPackageNamesSecondary()).isEmpty();
- }
-
- @Test
- public void testIdleJobFullRunWithFatalError() throws Exception {
- initUntilBootCompleted();
- runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
-
- doThrow(RuntimeException.class).when(mDexOptHelper).performDexOptWithStatus(any());
-
- runFullJob(mJobServiceForIdle, mJobParametersForIdle,
- /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_FATAL_ERROR,
- /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
- }
-
- @Test
- public void testSystemReadyWhenDisabled() throws Exception {
- when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true);
-
- mService.systemReady();
-
- verify(mContext, never()).registerReceiver(any(), any());
- }
-
- @Test
- public void testStopByCancelFlag() throws Exception {
- when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
- initUntilBootCompleted();
-
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
- ArgumentCaptor<Runnable> argDexOptThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
- verify(mInjector, atLeastOnce()).createAndStartThread(any(),
- argDexOptThreadRunnable.capture());
-
- // Stopping requires a separate thread
- HandlerThread cancelThread = new HandlerThread("Stopping");
- cancelThread.start();
- when(mInjector.createAndStartThread(any(), any())).thenReturn(cancelThread);
-
- // Cancel
- assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
- // Capture Runnable for cancel
- ArgumentCaptor<Runnable> argCancelThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
- verify(mInjector, atLeastOnce()).createAndStartThread(any(),
- argCancelThreadRunnable.capture());
-
- // Execute cancelling part
- cancelThread.getThreadHandler().post(argCancelThreadRunnable.getValue());
-
- verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(true);
-
- // Dexopt thread run and cancelled
- argDexOptThreadRunnable.getValue().run();
-
- // Wait until cancellation Runnable is completed.
- assertThat(cancelThread.getThreadHandler().runWithScissors(
- argCancelThreadRunnable.getValue(), TEST_WAIT_TIMEOUT_MS)).isTrue();
-
- // Now cancel completed
- verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
- verifyLastControlDexOptBlockingCall(false);
- }
-
- @Test
- public void testPostUpdateCancelFirst() throws Exception {
- initUntilBootCompleted();
- when(mInjector.createAndStartThread(any(), any())).thenAnswer(
- i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
- // Start
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
- // Cancel
- assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
- mCancelThread.runActualRunnable();
-
- // Wait until cancel has set the flag.
- verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(
- true);
-
- mDexOptThread.runActualRunnable();
-
- // All threads should finish.
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
- mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // Retry later if post boot job was cancelled
- verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
- verifyLastControlDexOptBlockingCall(false);
- }
-
- @Test
- public void testPostUpdateCancelLater() throws Exception {
- initUntilBootCompleted();
- when(mInjector.createAndStartThread(any(), any())).thenAnswer(
- i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
- // Start
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
- // Cancel
- assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
- // Dexopt thread runs and finishes
- mDexOptThread.runActualRunnable();
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
- mCancelThread.runActualRunnable();
- mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // Already completed before cancel, so no rescheduling.
- verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, false);
- verify(mDexOptHelper, never()).controlDexOptBlocking(true);
- }
-
- @Test
- public void testPeriodicJobCancelFirst() throws Exception {
- initUntilBootCompleted();
- when(mInjector.createAndStartThread(any(), any())).thenAnswer(
- i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
- // Start and finish post boot job
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
- mDexOptThread.runActualRunnable();
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // Start
- assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
- // Cancel
- assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-
- mCancelThread.runActualRunnable();
-
- // Wait until cancel has set the flag.
- verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(
- true);
-
- mDexOptThread.runActualRunnable();
-
- // All threads should finish.
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
- mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // The job should be rescheduled.
- verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true /* wantsReschedule */);
- verifyLastControlDexOptBlockingCall(false);
- }
-
- @Test
- public void testPeriodicJobCancelLater() throws Exception {
- initUntilBootCompleted();
- when(mInjector.createAndStartThread(any(), any())).thenAnswer(
- i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1)));
-
- // Start and finish post boot job
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
- mDexOptThread.runActualRunnable();
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // Start
- assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
- // Cancel
- assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue();
-
- // Dexopt thread finishes first.
- mDexOptThread.runActualRunnable();
- mDexOptThread.join(TEST_WAIT_TIMEOUT_MS);
-
- mCancelThread.runActualRunnable();
- mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
-
- // Always reschedule for periodic job
- verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
- verify(mDexOptHelper, never()).controlDexOptBlocking(true);
- }
-
- @Test
- public void testStopByThermal() throws Exception {
- when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
- initUntilBootCompleted();
-
- assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue();
-
- ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
- verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
-
- // Thermal cancel level
- when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL);
-
- argThreadRunnable.getValue().run();
-
- verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true);
- verifyLastControlDexOptBlockingCall(false);
- }
-
- @Test
- public void testRunShellCommandWithInvalidUid() {
- // Test uid cannot execute the command APIs
- assertThrows(SecurityException.class, () -> mService.runBackgroundDexoptJob(null));
- }
-
- @Test
- public void testCancelShellCommandWithInvalidUid() {
- // Test uid cannot execute the command APIs
- assertThrows(SecurityException.class, () -> mService.cancelBackgroundDexoptJob());
- }
-
- @Test
- public void testDisableJobSchedulerJobs() throws Exception {
- when(mInjector.getCallingUid()).thenReturn(Process.SHELL_UID);
- mService.setDisableJobSchedulerJobs(true);
- assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse();
- verify(mDexOptHelper, never()).performDexOpt(any());
- verify(mDexOptHelper, never()).performDexOptWithStatus(any());
- }
-
- @Test
- public void testSetDisableJobSchedulerJobsWithInvalidUid() {
- // Test uid cannot execute the command APIs
- assertThrows(SecurityException.class, () -> mService.setDisableJobSchedulerJobs(true));
- }
-
- private void initUntilBootCompleted() throws Exception {
- ArgumentCaptor<BroadcastReceiver> argReceiver = ArgumentCaptor.forClass(
- BroadcastReceiver.class);
- ArgumentCaptor<IntentFilter> argIntentFilter = ArgumentCaptor.forClass(IntentFilter.class);
-
- mService.systemReady();
-
- verify(mContext).registerReceiver(argReceiver.capture(), argIntentFilter.capture());
- assertThat(argIntentFilter.getValue().getAction(0)).isEqualTo(Intent.ACTION_BOOT_COMPLETED);
-
- argReceiver.getValue().onReceive(mContext, null);
-
- verify(mContext).unregisterReceiver(argReceiver.getValue());
- ArgumentCaptor<JobInfo> argJobs = ArgumentCaptor.forClass(JobInfo.class);
- verify(mJobScheduler, times(2)).schedule(argJobs.capture());
-
- List<Integer> expectedJobIds = Arrays.asList(BackgroundDexOptService.JOB_IDLE_OPTIMIZE,
- BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
- List<Integer> jobIds = argJobs.getAllValues().stream().map(job -> job.getId()).collect(
- Collectors.toList());
- assertThat(jobIds).containsExactlyElementsIn(expectedJobIds);
- }
-
- private void verifyLastControlDexOptBlockingCall(boolean expected) throws Exception {
- ArgumentCaptor<Boolean> argDexOptBlock = ArgumentCaptor.forClass(Boolean.class);
- verify(mDexOptHelper, atLeastOnce()).controlDexOptBlocking(argDexOptBlock.capture());
- assertThat(argDexOptBlock.getValue()).isEqualTo(expected);
- }
-
- private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params,
- boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams,
- @Nullable String expectedSkippedPackage) throws Exception {
- when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread());
- addFullRunSequence(expectedSkippedPackage);
- assertThat(mService.onStartJob(jobService, params)).isTrue();
-
- ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class);
- verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture());
-
- try {
- argThreadRunnable.getValue().run();
- } catch (RuntimeException e) {
- if (expectedStatus != STATUS_FATAL_ERROR) {
- throw e;
- }
- }
-
- verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params,
- expectedReschedule);
- // Never block
- verify(mDexOptHelper, never()).controlDexOptBlocking(true);
- if (expectedStatus != STATUS_FATAL_ERROR) {
- verifyPerformDexOpt();
- }
- assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus);
- }
-
- private void verifyPerformDexOpt() {
- InOrder inOrder = inOrder(mDexOptHelper);
- inOrder.verify(mDexOptHelper).getOptimizablePackages(any());
- for (DexOptInfo info : mDexInfoSequence) {
- if (info.isPrimary) {
- verify(mDexOptHelper).performDexOptWithStatus(
- argThat((option) -> option.getPackageName().equals(info.packageName)
- && !option.isDexoptOnlySecondaryDex()));
- } else {
- inOrder.verify(mDexOptHelper).performDexOpt(
- argThat((option) -> option.getPackageName().equals(info.packageName)
- && option.isDexoptOnlySecondaryDex()));
- }
- }
-
- // Even InOrder cannot check the order if the same call is made multiple times.
- // To check the order across multiple runs, we reset the mock so that order can be checked
- // in each call.
- mDexInfoSequence.clear();
- reset(mDexOptHelper);
- setupDexOptHelper();
- }
-
- private String findDumpValueForKey(String key) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- PrintWriter pw = new PrintWriter(out, true);
- IndentingPrintWriter writer = new IndentingPrintWriter(pw, "");
- try {
- mService.dump(writer);
- writer.flush();
- Log.i(TAG, "dump output:" + out.toString());
- for (String line : out.toString().split(System.lineSeparator())) {
- String[] vals = line.split(":");
- if (vals[0].equals(key)) {
- if (vals.length == 2) {
- return vals[1].strip();
- } else {
- break;
- }
- }
- }
- return "";
- } finally {
- writer.close();
- }
- }
-
- List<String> findStringListFromDump(String key) {
- String values = findDumpValueForKey(key);
- if (values.isEmpty()) {
- return Collections.emptyList();
- }
- return Arrays.asList(values.split(","));
- }
-
- private List<String> getFailedPackageNamesPrimary() {
- return findStringListFromDump("mFailedPackageNamesPrimary");
- }
-
- private List<String> getFailedPackageNamesSecondary() {
- return findStringListFromDump("mFailedPackageNamesSecondary");
- }
-
- private int getLastExecutionStatus() {
- return Integer.parseInt(findDumpValueForKey("mLastExecutionStatus"));
- }
-
- private static class DexOptInfo {
- public final String packageName;
- public final boolean isPrimary;
-
- private DexOptInfo(String packageName, boolean isPrimary) {
- this.packageName = packageName;
- this.isPrimary = isPrimary;
- }
- }
-
- private void addFullRunSequence(@Nullable String expectedSkippedPackage) {
- for (String packageName : DEFAULT_PACKAGE_LIST) {
- if (packageName.equals(expectedSkippedPackage)) {
- // only fails primary dexopt in mocking but add secodary
- mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
- } else {
- mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ true));
- mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false));
- }
- }
- }
-
- private static class StartAndWaitThread extends Thread {
- private final Runnable mActualRunnable;
- private final CountDownLatch mLatch = new CountDownLatch(1);
-
- private StartAndWaitThread(String name, Runnable runnable) {
- super(name);
- mActualRunnable = runnable;
- }
-
- private void runActualRunnable() {
- mLatch.countDown();
- }
-
- @Override
- public void run() {
- // Thread is started but does not run actual code. This is for controlling the execution
- // order while still meeting Thread.isAlive() check.
- try {
- mLatch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- mActualRunnable.run();
- }
- }
-
- private Thread createAndStartExecutionThread(String name, Runnable runnable) {
- final boolean isDexOptThread = !name.equals("DexOptCancel");
- StartAndWaitThread thread = new StartAndWaitThread(name, runnable);
- if (isDexOptThread) {
- mDexOptThread = thread;
- } else {
- mCancelThread = thread;
- }
- thread.start();
- return thread;
- }
-}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 37967fa86b0f..65986ea063fe 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -62,6 +62,7 @@ android_test {
"cts-wm-util",
"platform-compat-test-rules",
"mockito-target-minus-junit4",
+ "mockito-kotlin2",
"platform-test-annotations",
"ShortcutManagerTestUtils",
"truth",
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b2ecea1b0302..53c460c44354 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -50,9 +50,11 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.app.PendingIntent;
import android.app.RemoteAction;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -67,6 +69,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -74,6 +77,7 @@ import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.Display;
import android.view.DisplayAdjustments;
@@ -123,6 +127,7 @@ import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1464,6 +1469,52 @@ public class AccessibilityManagerServiceTest {
AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
}
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() {
+ String daltonizerTile =
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
+ String colorInversionTile =
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
+ final AccessibilityUserState userState = new AccessibilityUserState(
+ UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
+ mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.ACCESSIBILITY_QS_TARGETS)
+ .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, colorInversionTile);
+ sendBroadcastToAccessibilityManagerService(intent);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets())
+ .containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() {
+ String daltonizerTile =
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
+ String colorInversionTile =
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
+ final AccessibilityUserState userState = new AccessibilityUserState(
+ UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
+ userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
+ mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
+
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .putExtra(Intent.EXTRA_SETTING_NAME, Settings.Secure.ACCESSIBILITY_QS_TARGETS)
+ .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, colorInversionTile);
+ sendBroadcastToAccessibilityManagerService(intent);
+ mTestableLooper.processAllMessages();
+
+ assertThat(userState.getA11yQsTargets())
+ .containsExactlyElementsIn(Set.of(daltonizerTile));
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
return mockAccessibilityServiceInfo(
@@ -1542,6 +1593,14 @@ public class AccessibilityManagerServiceTest {
mA11yms.getCurrentUserState().updateTileServiceMapForAccessibilityServiceLocked();
}
+ private void sendBroadcastToAccessibilityManagerService(Intent intent) {
+ if (!mTestableContext.getBroadcastReceivers().containsKey(intent.getAction())) {
+ return;
+ }
+ mTestableContext.getBroadcastReceivers().get(intent.getAction()).forEach(
+ broadcastReceiver -> broadcastReceiver.onReceive(mTestableContext, intent));
+ }
+
public static class FakeInputFilter extends AccessibilityInputFilter {
FakeInputFilter(Context context,
AccessibilityManagerService service) {
@@ -1552,6 +1611,7 @@ public class AccessibilityManagerServiceTest {
private static class A11yTestableContext extends TestableContext {
private final Context mMockContext;
+ private final Map<String, List<BroadcastReceiver>> mBroadcastReceivers = new ArrayMap<>();
A11yTestableContext(Context base) {
super(base);
@@ -1563,8 +1623,29 @@ public class AccessibilityManagerServiceTest {
mMockContext.startActivityAsUser(intent, options, user);
}
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ Iterator<String> actions = filter.actionsIterator();
+ if (actions != null) {
+ while (actions.hasNext()) {
+ String action = actions.next();
+ List<BroadcastReceiver> actionReceivers =
+ mBroadcastReceivers.getOrDefault(action, new ArrayList<>());
+ actionReceivers.add(receiver);
+ mBroadcastReceivers.put(action, actionReceivers);
+ }
+ }
+ return super.registerReceiverAsUser(
+ receiver, user, filter, broadcastPermission, scheduler);
+ }
+
Context getMockContext() {
return mMockContext;
}
+
+ Map<String, List<BroadcastReceiver>> getBroadcastReceivers() {
+ return mBroadcastReceivers;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7891661f6338..643dcec27bfd 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -111,6 +111,8 @@ import com.android.server.pm.UserTypeFactory;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;
+import com.google.common.collect.Range;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -155,6 +157,7 @@ public class UserControllerTest {
private UserController mUserController;
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
+ private final HashMap<Integer, UserInfo> mUserInfos = new HashMap<>();
private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
@@ -587,25 +590,25 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- int numerOfUserSwitches = 1;
+ int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numerOfUserSwitches, false);
+ numberOfUserSwitches, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
mUserController.getRunningUsersLU());
- numerOfUserSwitches++;
+ numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numerOfUserSwitches, false);
+ numberOfUserSwitches, false);
// running: user 0, USER_ID, USER_ID1
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
mUserController.getRunningUsersLU());
- numerOfUserSwitches++;
+ numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numerOfUserSwitches, false);
+ numberOfUserSwitches, false);
UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
// skip middle step and call this directly.
mUserController.finishUserSwitch(ussUser2);
@@ -631,20 +634,20 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- int numerOfUserSwitches = 1;
+ int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numerOfUserSwitches, false);
+ numberOfUserSwitches, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
mUserController.getRunningUsersLU());
- numerOfUserSwitches++;
+ numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numerOfUserSwitches, true);
+ numberOfUserSwitches, true);
// running: user 0, USER_ID1
// stopped + unlocked: USER_ID
- numerOfUserSwitches++;
+ numberOfUserSwitches++;
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1}),
mUserController.getRunningUsersLU());
@@ -659,7 +662,7 @@ public class UserControllerTest {
.lockCeStorage(anyInt());
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numerOfUserSwitches, true);
+ numberOfUserSwitches, true);
// running: user 0, USER_ID2
// stopped + unlocked: USER_ID1
// stopped + locked: USER_ID
@@ -675,6 +678,105 @@ public class UserControllerTest {
}
/**
+ * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was
+ * started afterwards.
+ */
+ @Test
+ public void testRunningUsersListOrder_parentAfterProfile() {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+
+ final int PARENT_ID = 200;
+ final int PROFILE1_ID = 201;
+ final int PROFILE2_ID = 202;
+ final int FG_USER_ID = 300;
+ final int BG_USER_ID = 400;
+
+ setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE1_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE2_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(FG_USER_ID, 0).profileGroupId = FG_USER_ID;
+ setUpUser(BG_USER_ID, 0).profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID}),
+ mUserController.getRunningUsersLU());
+
+ int numberOfUserSwitches = 1;
+ addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
+ numberOfUserSwitches, false);
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
+ mUserController.getRunningUsersLU());
+
+ assertThat(mUserController.startProfile(PROFILE1_ID, true, null)).isTrue();
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID}),
+ mUserController.getRunningUsersLU());
+
+ numberOfUserSwitches++;
+ addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
+ numberOfUserSwitches, false);
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
+ mUserController.getRunningUsersLU());
+
+ mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, BG_USER_ID, FG_USER_ID}),
+ mUserController.getRunningUsersLU());
+
+ assertThat(mUserController.startProfile(PROFILE2_ID, true, null)).isTrue();
+ // Note for the future:
+ // It is not absolutely essential that PROFILE1 come before PROFILE2,
+ // nor that PROFILE1 come before BG_USER. We can change that policy later if we'd like.
+ // The important thing is that PROFILE1 and PROFILE2 precede PARENT,
+ // and that everything precedes OTHER.
+ assertEquals(Arrays.asList(new Integer[] {
+ SYSTEM_USER_ID, PROFILE1_ID, BG_USER_ID, PROFILE2_ID, PARENT_ID, FG_USER_ID}),
+ mUserController.getRunningUsersLU());
+ }
+
+ /**
+ * Test that, in getRunningUsersLU, the current user is always at the end, even if background
+ * users were started subsequently.
+ */
+ @Test
+ public void testRunningUsersListOrder_currentAtEnd() {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+
+ final int CURRENT_ID = 200;
+ final int PROFILE_ID = 201;
+ final int BG_USER_ID = 400;
+
+ setUpUser(CURRENT_ID, 0).profileGroupId = CURRENT_ID;
+ setUpUser(PROFILE_ID, UserInfo.FLAG_PROFILE).profileGroupId = CURRENT_ID;
+ setUpUser(BG_USER_ID, 0).profileGroupId = BG_USER_ID;
+ mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID}),
+ mUserController.getRunningUsersLU());
+
+ addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
+ mUserController.getRunningUsersLU());
+
+ mUserController.startUser(BG_USER_ID, USER_START_MODE_BACKGROUND);
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, BG_USER_ID, CURRENT_ID}),
+ mUserController.getRunningUsersLU());
+
+ assertThat(mUserController.startProfile(PROFILE_ID, true, null)).isTrue();
+ assertEquals(Arrays.asList(
+ new Integer[] {SYSTEM_USER_ID, BG_USER_ID, PROFILE_ID, CURRENT_ID}),
+ mUserController.getRunningUsersLU());
+ }
+
+ /**
* Test locking user with mDelayUserDataLocking false.
*/
@Test
@@ -785,6 +887,52 @@ public class UserControllerTest {
verifyUserUnassignedFromDisplayNeverCalled(TEST_USER_ID);
}
+ /** Test that stopping a profile doesn't also stop its parent, even if it's in background. */
+ @Test
+ public void testStopProfile_doesNotStopItsParent() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+
+ final Range<Integer> RUNNING_RANGE =
+ Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
+
+ final int PARENT_ID = TEST_USER_ID1;
+ final int PROFILE_ID = TEST_USER_ID2;
+ final int OTHER_ID = TEST_USER_ID3;
+
+ setUpUser(PARENT_ID, 0).profileGroupId = PARENT_ID;
+ setUpUser(PROFILE_ID, UserInfo.FLAG_PROFILE).profileGroupId = PARENT_ID;
+ setUpUser(OTHER_ID, 0).profileGroupId = OTHER_ID;
+ mUserController.onSystemReady(); // To set the profileGroupIds in UserController.
+
+ // Start the parent in the background
+ boolean started = mUserController.startUser(PARENT_ID, USER_START_MODE_BACKGROUND);
+ assertWithMessage("startUser(%s)", PARENT_ID).that(started).isTrue();
+ assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+
+ // Start the profile
+ started = mUserController.startProfile(PROFILE_ID, true, null);
+ assertWithMessage("startProfile(%s)", PROFILE_ID).that(started).isTrue();
+ assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+ assertThat(mUserController.getStartedUserState(PROFILE_ID).state).isIn(RUNNING_RANGE);
+
+ // Start an unrelated user
+ started = mUserController.startUser(OTHER_ID, USER_START_MODE_FOREGROUND);
+ assertWithMessage("startUser(%s)", OTHER_ID).that(started).isTrue();
+ assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+ assertThat(mUserController.getStartedUserState(PROFILE_ID).state).isIn(RUNNING_RANGE);
+ assertThat(mUserController.getStartedUserState(OTHER_ID).state).isIn(RUNNING_RANGE);
+
+ // Stop the profile and assert that its (background) parent didn't stop too
+ boolean stopped = mUserController.stopProfile(PROFILE_ID);
+ assertWithMessage("stopProfile(%s)", PROFILE_ID).that(stopped).isTrue();
+ if (mUserController.getStartedUserState(PROFILE_ID) != null) {
+ assertThat(mUserController.getStartedUserState(PROFILE_ID).state)
+ .isNotIn(RUNNING_RANGE);
+ }
+ assertThat(mUserController.getStartedUserState(PARENT_ID).state).isIn(RUNNING_RANGE);
+ }
+
@Test
public void testStartProfile_disabledProfileFails() {
setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */
@@ -1152,11 +1300,11 @@ public class UserControllerTest {
continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
}
- private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
- setUpUser(userId, flags, /* preCreated= */ false, /* userType */ null);
+ private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
+ return setUpUser(userId, flags, /* preCreated= */ false, /* userType */ null);
}
- private void setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated,
+ private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags, boolean preCreated,
@Nullable String userType) {
if (userType == null) {
userType = UserInfo.getDefaultUserType(flags);
@@ -1171,6 +1319,12 @@ public class UserControllerTest {
assertThat(userTypeDetails).isNotNull();
when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId)))
.thenReturn(userTypeDetails.getDefaultUserPropertiesReference());
+
+ mUserInfos.put(userId, userInfo);
+ when(mInjector.mUserManagerMock.getUsers(anyBoolean()))
+ .thenReturn(mUserInfos.values().stream().toList());
+
+ return userInfo;
}
private static List<String> getActions(List<Intent> intents) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 4f6fc3dc1f93..0a696ef44897 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -47,7 +47,7 @@ import android.view.inputmethod.InputMethodInfo;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
@@ -67,9 +67,7 @@ import java.util.Set;
/**
* Run this test with:
- *
* {@code atest FrameworksServicesTests:com.android.server.devicepolicy.OwnersTest}
- *
*/
@RunWith(AndroidJUnit4.class)
public class OverlayPackagesProviderTest {
@@ -87,8 +85,8 @@ public class OverlayPackagesProviderTest {
private FakePackageManager mPackageManager;
private String[] mSystemAppsWithLauncher;
- private Set<String> mRegularMainlineModules = new HashSet<>();
- private Map<String, String> mMainlineModuleToDeclaredMetadataMap = new HashMap<>();
+ private final Set<String> mRegularMainlineModules = new HashSet<>();
+ private final Map<String, String> mMainlineModuleToDeclaredMetadataMap = new HashMap<>();
private OverlayPackagesProvider mHelper;
@Before
@@ -115,7 +113,8 @@ public class OverlayPackagesProviderTest {
setVendorDisallowedAppsManagedUser();
mRealResources = InstrumentationRegistry.getTargetContext().getResources();
- mHelper = new OverlayPackagesProvider(mTestContext, mInjector);
+ mHelper = new OverlayPackagesProvider(mTestContext, mInjector,
+ new RecursiveStringArrayResourceResolver(mResources));
}
@Test
@@ -213,7 +212,7 @@ public class OverlayPackagesProviderTest {
}
/**
- * @see {@link #testAllowedAndDisallowedAtTheSameTimeManagedDevice}
+ * @see #testAllowedAndDisallowedAtTheSameTimeManagedDevice
*/
@Test
public void testAllowedAndDisallowedAtTheSameTimeManagedUser() {
@@ -224,7 +223,7 @@ public class OverlayPackagesProviderTest {
}
/**
- * @see {@link #testAllowedAndDisallowedAtTheSameTimeManagedDevice}
+ * @see #testAllowedAndDisallowedAtTheSameTimeManagedDevice
*/
@Test
public void testAllowedAndDisallowedAtTheSameTimeManagedProfile() {
@@ -447,7 +446,7 @@ public class OverlayPackagesProviderTest {
}
private void setSystemInputMethods(String... packageNames) {
- List<InputMethodInfo> inputMethods = new ArrayList<InputMethodInfo>();
+ List<InputMethodInfo> inputMethods = new ArrayList<>();
for (String packageName : packageNames) {
ApplicationInfo aInfo = new ApplicationInfo();
aInfo.flags = ApplicationInfo.FLAG_SYSTEM;
@@ -467,6 +466,7 @@ public class OverlayPackagesProviderTest {
mSystemAppsWithLauncher = apps;
}
+ @SafeVarargs
private <T> Set<T> setFromArray(T... array) {
if (array == null) {
return null;
@@ -475,6 +475,7 @@ public class OverlayPackagesProviderTest {
}
class FakePackageManager extends MockPackageManager {
+ @NonNull
@Override
public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
assertWithMessage("Expected an intent with action ACTION_MAIN")
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/RecursiveStringArrayResourceResolverTest.kt b/services/tests/servicestests/src/com/android/server/devicepolicy/RecursiveStringArrayResourceResolverTest.kt
new file mode 100644
index 000000000000..647f6c78f29f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/RecursiveStringArrayResourceResolverTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy
+
+import android.annotation.ArrayRes
+import android.content.res.Resources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.errorprone.annotations.CanIgnoreReturnValue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Run this test with:
+ * `atest FrameworksServicesTests:com.android.server.devicepolicy.RecursiveStringArrayResourceResolverTest`
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RecursiveStringArrayResourceResolverTest {
+ private companion object {
+ const val PACKAGE = "com.android.test"
+ const val ROOT_RESOURCE = "my_root_resource"
+ const val SUB_RESOURCE = "my_sub_resource"
+ const val EXTERNAL_PACKAGE = "com.external.test"
+ const val EXTERNAL_RESOURCE = "my_external_resource"
+ }
+
+ private val mResources = mock<Resources>()
+ private val mTarget = RecursiveStringArrayResourceResolver(mResources)
+
+ /**
+ * Mocks [Resources.getIdentifier] and [Resources.getStringArray] to return [values] and reference under a generated ID.
+ * @receiver mocked [Resources] container to configure
+ * @param pkg package name to "contain" mocked resource
+ * @param name mocked resource name
+ * @param values string-array resource values to return when mock is queried
+ * @return generated resource ID
+ */
+ @ArrayRes
+ @CanIgnoreReturnValue
+ private fun Resources.mockStringArrayResource(pkg: String, name: String, vararg values: String): Int {
+ val anId = (pkg + name).hashCode()
+ println("Mocking Resources::getIdentifier(name=\"$name\", defType=\"array\", defPackage=\"$pkg\") -> $anId")
+ whenever(getIdentifier(eq(name), eq("array"), eq(pkg))).thenReturn(anId)
+ println("Mocking Resources::getStringArray(id=$anId) -> ${values.asList()}")
+ whenever(getStringArray(eq(anId))).thenReturn(values)
+ return anId
+ }
+
+ @Test
+ fun testCanResolveTheArrayWithoutImports() {
+ val values = arrayOf("app.a", "app.b")
+ val mockId = mResources.mockStringArrayResource(pkg = PACKAGE, name = ROOT_RESOURCE, values = values)
+
+ val actual = mTarget.resolve(/* pkg= */ PACKAGE, /* rootId = */ mockId)
+
+ assertWithMessage("Values are resolved correctly")
+ .that(actual).containsExactlyElementsIn(values)
+ }
+
+ @Test
+ fun testCanResolveTheArrayWithImports() {
+ val externalValues = arrayOf("ext.a", "ext.b", "#import:$PACKAGE/$SUB_RESOURCE")
+ mResources.mockStringArrayResource(pkg = EXTERNAL_PACKAGE, name = EXTERNAL_RESOURCE, values = externalValues)
+ val subValues = arrayOf("sub.a", "sub.b")
+ mResources.mockStringArrayResource(pkg = PACKAGE, name = SUB_RESOURCE, values = subValues)
+ val values = arrayOf("app.a", "#import:./$SUB_RESOURCE", "app.b", "#import:$EXTERNAL_PACKAGE/$EXTERNAL_RESOURCE", "app.c")
+ val mockId = mResources.mockStringArrayResource(pkg = PACKAGE, name = ROOT_RESOURCE, values = values)
+
+ val actual = mTarget.resolve(/* pkg= */ PACKAGE, /* rootId= */ mockId)
+
+ assertWithMessage("Values are resolved correctly")
+ .that(actual).containsExactlyElementsIn((externalValues + subValues + values)
+ .filterNot { it.startsWith("#import:") }
+ .toSet())
+ }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 8cbcc226ce73..5861d88924e0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -500,7 +500,8 @@ public class VibratorManagerServiceTest {
InOrder batteryVerifier = inOrder(mBatteryStatsMock);
batteryVerifier.verify(mBatteryStatsMock)
.noteVibratorOn(UID, oneShotDuration + mVibrationConfig.getRampDownDurationMs());
- batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID);
+ batteryVerifier
+ .verify(mBatteryStatsMock, timeout(TEST_TIMEOUT_MILLIS)).noteVibratorOff(UID);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 0a29dfbd7db7..60716cbbb693 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -95,8 +95,6 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase {
new int[]{KeyEvent.KEYCODE_NOTIFICATION},
KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
0},
- {"Meta + T -> Toggle Taskbar", new int[]{META_KEY, KeyEvent.KEYCODE_T},
- KeyboardLogEvent.TOGGLE_TASKBAR, KeyEvent.KEYCODE_T, META_ON},
{"Meta + Ctrl + S -> Take Screenshot",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c29547f123aa..b9e87dc6efce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -633,18 +633,23 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Test
public void testAdjacentFocusInActivityEmbedding() {
mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
- Task task = createTask(mDefaultDisplay);
- TaskFragment primary = createTaskFragmentWithActivity(task);
- TaskFragment secondary = createTaskFragmentWithActivity(task);
- primary.setAdjacentTaskFragment(secondary);
- secondary.setAdjacentTaskFragment(primary);
-
- WindowState windowState = mock(WindowState.class);
+ final Task task = createTask(mDefaultDisplay);
+ final TaskFragment primaryTf = createTaskFragmentWithActivity(task);
+ final TaskFragment secondaryTf = createTaskFragmentWithActivity(task);
+ final ActivityRecord primaryActivity = primaryTf.getTopMostActivity();
+ final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity();
+ primaryTf.setAdjacentTaskFragment(secondaryTf);
+ secondaryTf.setAdjacentTaskFragment(primaryTf);
+
+ final WindowState windowState = mock(WindowState.class);
+ windowState.mActivityRecord = primaryActivity;
doReturn(windowState).when(mWm).getFocusedWindowLocked();
- doReturn(primary).when(windowState).getTaskFragment();
+ doReturn(primaryTf).when(windowState).getTaskFragment();
+ doReturn(1L).when(primaryActivity).getLastWindowCreateTime();
+ doReturn(2L).when(secondaryActivity).getLastWindowCreateTime();
startBackNavigation();
- verify(mWm).moveFocusToActivity(any());
+ verify(mWm).moveFocusToActivity(eq(secondaryActivity));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 5360a1033eb4..6b1bf26bfdff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -887,20 +887,14 @@ public class TaskFragmentTest extends WindowTestsBase {
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
if (Flags.embeddedActivityBackNavFlag()) {
- // Send request to move the focus to top window from the left window.
- assertTrue(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
- // The focus should change.
- assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
-
- // Send request to move the focus to top window from the right window.
- assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
- // The focus should NOT change.
- assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
-
- // Do not move focus if the dim is boosted.
- taskFragmentLeft.mDimmerSurfaceBoosted = true;
- assertFalse(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
- assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+ // Move focus if the adjacent activity is more recently active.
+ doReturn(1L).when(appLeftTop).getLastWindowCreateTime();
+ doReturn(2L).when(appRightTop).getLastWindowCreateTime();
+ assertTrue(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
+
+ // Do not move the focus if the adjacent activity is less recently active.
+ doReturn(3L).when(appLeftTop).getLastWindowCreateTime();
+ assertFalse(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 3bd6496a01dd..a88680a002b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1945,6 +1945,21 @@ public class TaskTests extends WindowTestsBase {
assertEquals(2, finishCount[0]);
}
+ @Test
+ public void testPauseActivityWhenHasEmptyLeafTaskFragment() {
+ // Creating a task that has a RESUMED activity and an empty TaskFragment.
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopMostActivity();
+ new TaskFragmentBuilder(mAtm).setParentTask(task).build();
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+
+ // Ensure the activity is paused if cannot be resumed.
+ doReturn(false).when(task).canBeResumed(any());
+ mSupervisor.mUserLeaving = true;
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 70a95400bd9e..d65a4e44440e 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -14,37 +14,22 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
// Build the android.test.base library
// ===================================
// This contains the junit.framework and android.test classes that were in
// Android API level 25 excluding those from android.test.runner.
// Also contains the com.android.internal.util.Predicate[s] classes.
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- // SPDX-license-identifier-CPL-1.0
- default_applicable_licenses: ["frameworks_base_test-base_license"],
-}
-
-license {
- name: "frameworks_base_test-base_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- "SPDX-license-identifier-CPL-1.0",
- ],
- license_text: [
- "src/junit/cpl-v10.html",
- ],
-}
-
java_sdk_library {
name: "android.test.base",
- srcs: [":android-test-base-sources"],
+ srcs: [
+ ":android-test-base-sources",
+ ":frameworks-base-test-junit-framework",
+ ],
errorprone: {
javacflags: ["-Xep:DepAnn:ERROR"],
@@ -84,7 +69,10 @@ java_library_static {
],
installable: false,
- srcs: [":android-test-base-sources"],
+ srcs: [
+ ":android-test-base-sources",
+ ":frameworks-base-test-junit-framework",
+ ],
errorprone: {
javacflags: ["-Xep:DepAnn:ERROR"],
@@ -104,8 +92,7 @@ java_library_static {
name: "android.test.base-minus-junit",
srcs: [
- "src/android/**/*.java",
- "src/com/**/*.java",
+ "src/**/*.java",
],
sdk_version: "current",
diff --git a/test-base/hiddenapi/Android.bp b/test-base/hiddenapi/Android.bp
index 1466590ef311..4c59b10ba423 100644
--- a/test-base/hiddenapi/Android.bp
+++ b/test-base/hiddenapi/Android.bp
@@ -15,12 +15,7 @@
//
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
// Provided solely to contribute information about which hidden parts of the android.test.base
diff --git a/test-junit/Android.bp b/test-junit/Android.bp
new file mode 100644
index 000000000000..8d3d439e034e
--- /dev/null
+++ b/test-junit/Android.bp
@@ -0,0 +1,53 @@
+//
+// 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 {
+ default_applicable_licenses: ["frameworks-base-test-junit-license"],
+}
+
+license {
+ name: "frameworks-base-test-junit-license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-CPL-1.0",
+ ],
+ license_text: [
+ "src/junit/cpl-v10.html",
+ ],
+}
+
+filegroup {
+ name: "frameworks-base-test-junit-framework",
+ srcs: [
+ "src/junit/framework/**/*.java",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/base/test-base",
+ ],
+}
+
+filegroup {
+ name: "frameworks-base-test-junit-runner",
+ srcs: [
+ "src/junit/runner/**/*.java",
+ "src/junit/textui/**/*.java",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/base/test-runner",
+ ],
+}
diff --git a/test-base/src/junit/MODULE_LICENSE_CPL b/test-junit/src/junit/MODULE_LICENSE_CPL
index e69de29bb2d1..e69de29bb2d1 100644
--- a/test-base/src/junit/MODULE_LICENSE_CPL
+++ b/test-junit/src/junit/MODULE_LICENSE_CPL
diff --git a/test-base/src/junit/README.android b/test-junit/src/junit/README.android
index 1384a1fedda2..1384a1fedda2 100644
--- a/test-base/src/junit/README.android
+++ b/test-junit/src/junit/README.android
diff --git a/test-base/src/junit/cpl-v10.html b/test-junit/src/junit/cpl-v10.html
index 36aa208d4a29..36aa208d4a29 100644
--- a/test-base/src/junit/cpl-v10.html
+++ b/test-junit/src/junit/cpl-v10.html
diff --git a/test-base/src/junit/framework/Assert.java b/test-junit/src/junit/framework/Assert.java
index 3dcc23d71c19..3dcc23d71c19 100644
--- a/test-base/src/junit/framework/Assert.java
+++ b/test-junit/src/junit/framework/Assert.java
diff --git a/test-base/src/junit/framework/AssertionFailedError.java b/test-junit/src/junit/framework/AssertionFailedError.java
index 0d7802c431c6..0d7802c431c6 100644
--- a/test-base/src/junit/framework/AssertionFailedError.java
+++ b/test-junit/src/junit/framework/AssertionFailedError.java
diff --git a/test-base/src/junit/framework/ComparisonCompactor.java b/test-junit/src/junit/framework/ComparisonCompactor.java
index e540f03b87d3..e540f03b87d3 100644
--- a/test-base/src/junit/framework/ComparisonCompactor.java
+++ b/test-junit/src/junit/framework/ComparisonCompactor.java
diff --git a/test-base/src/junit/framework/ComparisonFailure.java b/test-junit/src/junit/framework/ComparisonFailure.java
index 507799328a44..507799328a44 100644
--- a/test-base/src/junit/framework/ComparisonFailure.java
+++ b/test-junit/src/junit/framework/ComparisonFailure.java
diff --git a/test-base/src/junit/framework/Protectable.java b/test-junit/src/junit/framework/Protectable.java
index e1432370cfaf..e1432370cfaf 100644
--- a/test-base/src/junit/framework/Protectable.java
+++ b/test-junit/src/junit/framework/Protectable.java
diff --git a/test-base/src/junit/framework/Test.java b/test-junit/src/junit/framework/Test.java
index a016ee8308f1..a016ee8308f1 100644
--- a/test-base/src/junit/framework/Test.java
+++ b/test-junit/src/junit/framework/Test.java
diff --git a/test-base/src/junit/framework/TestCase.java b/test-junit/src/junit/framework/TestCase.java
index b047ec9e1afc..b047ec9e1afc 100644
--- a/test-base/src/junit/framework/TestCase.java
+++ b/test-junit/src/junit/framework/TestCase.java
diff --git a/test-base/src/junit/framework/TestFailure.java b/test-junit/src/junit/framework/TestFailure.java
index 6662b1fab1b2..6662b1fab1b2 100644
--- a/test-base/src/junit/framework/TestFailure.java
+++ b/test-junit/src/junit/framework/TestFailure.java
diff --git a/test-base/src/junit/framework/TestListener.java b/test-junit/src/junit/framework/TestListener.java
index 9b6944361b9d..9b6944361b9d 100644
--- a/test-base/src/junit/framework/TestListener.java
+++ b/test-junit/src/junit/framework/TestListener.java
diff --git a/test-base/src/junit/framework/TestResult.java b/test-junit/src/junit/framework/TestResult.java
index 3052e94074fd..3052e94074fd 100644
--- a/test-base/src/junit/framework/TestResult.java
+++ b/test-junit/src/junit/framework/TestResult.java
diff --git a/test-base/src/junit/framework/TestSuite.java b/test-junit/src/junit/framework/TestSuite.java
index 336efd1800d7..336efd1800d7 100644
--- a/test-base/src/junit/framework/TestSuite.java
+++ b/test-junit/src/junit/framework/TestSuite.java
diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-junit/src/junit/runner/BaseTestRunner.java
index b2fa16c91da2..b2fa16c91da2 100644
--- a/test-runner/src/junit/runner/BaseTestRunner.java
+++ b/test-junit/src/junit/runner/BaseTestRunner.java
diff --git a/test-runner/src/junit/runner/StandardTestSuiteLoader.java b/test-junit/src/junit/runner/StandardTestSuiteLoader.java
index 808963a5aea0..808963a5aea0 100644
--- a/test-runner/src/junit/runner/StandardTestSuiteLoader.java
+++ b/test-junit/src/junit/runner/StandardTestSuiteLoader.java
diff --git a/test-runner/src/junit/runner/TestRunListener.java b/test-junit/src/junit/runner/TestRunListener.java
index 0e9581989eee..0e9581989eee 100644
--- a/test-runner/src/junit/runner/TestRunListener.java
+++ b/test-junit/src/junit/runner/TestRunListener.java
diff --git a/test-runner/src/junit/runner/TestSuiteLoader.java b/test-junit/src/junit/runner/TestSuiteLoader.java
index 9cc6d81e125e..9cc6d81e125e 100644
--- a/test-runner/src/junit/runner/TestSuiteLoader.java
+++ b/test-junit/src/junit/runner/TestSuiteLoader.java
diff --git a/test-runner/src/junit/runner/Version.java b/test-junit/src/junit/runner/Version.java
index dd88c03372c8..dd88c03372c8 100644
--- a/test-runner/src/junit/runner/Version.java
+++ b/test-junit/src/junit/runner/Version.java
diff --git a/test-runner/src/junit/runner/package-info.java b/test-junit/src/junit/runner/package-info.java
index 364e3621456e..364e3621456e 100644
--- a/test-runner/src/junit/runner/package-info.java
+++ b/test-junit/src/junit/runner/package-info.java
diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-junit/src/junit/textui/ResultPrinter.java
index b4914529bf4f..b4914529bf4f 100644
--- a/test-runner/src/junit/textui/ResultPrinter.java
+++ b/test-junit/src/junit/textui/ResultPrinter.java
diff --git a/test-runner/src/junit/textui/TestRunner.java b/test-junit/src/junit/textui/TestRunner.java
index 046448e5e76a..046448e5e76a 100644
--- a/test-runner/src/junit/textui/TestRunner.java
+++ b/test-junit/src/junit/textui/TestRunner.java
diff --git a/test-runner/src/junit/textui/package-info.java b/test-junit/src/junit/textui/package-info.java
index 28b2ef46b582..28b2ef46b582 100644
--- a/test-runner/src/junit/textui/package-info.java
+++ b/test-junit/src/junit/textui/package-info.java
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index f37d2d17973e..e29d321e5105 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -17,12 +17,7 @@
// Build the android.test.mock library
// ===================================
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
java_sdk_library {
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 21e09d3221ce..6b5be3cba204 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -14,29 +14,19 @@
// limitations under the License.
//
-// Build the android.test.runner library
-// =====================================
package {
- // See: http://go/android-license-faq
- default_applicable_licenses: ["frameworks_base_test-runner_license"],
-}
-
-license {
- name: "frameworks_base_test-runner_license",
- visibility: [":__subpackages__"],
- license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
- "SPDX-license-identifier-CPL-1.0",
- ],
- license_text: [
- "src/junit/cpl-v10.html",
- ],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
+// Build the android.test.runner library
+// =====================================
java_sdk_library {
name: "android.test.runner",
- srcs: [":android-test-runner-sources"],
+ srcs: [
+ ":android-test-runner-sources",
+ ":frameworks-base-test-junit-runner",
+ ],
errorprone: {
javacflags: ["-Xep:DepAnn:ERROR"],
diff --git a/test-runner/src/junit/MODULE_LICENSE_CPL b/test-runner/src/junit/MODULE_LICENSE_CPL
deleted file mode 100644
index e69de29bb2d1..000000000000
--- a/test-runner/src/junit/MODULE_LICENSE_CPL
+++ /dev/null
diff --git a/test-runner/src/junit/README.android b/test-runner/src/junit/README.android
deleted file mode 100644
index 1384a1fedda2..000000000000
--- a/test-runner/src/junit/README.android
+++ /dev/null
@@ -1,11 +0,0 @@
-URL: https://github.com/junit-team/junit4
-License: Common Public License Version 1.0
-License File: cpl-v10.html
-
-This is JUnit 4.10 source that was previously part of the Android Public API.
-Where necessary it has been patched to be compatible (according to Android API
-requirements) with JUnit 3.8.
-
-These are copied here to ensure that the android.test.runner target remains
-compatible with the last version of the Android API (25) that contained these
-classes even when external/junit is upgraded to a later version.
diff --git a/test-runner/src/junit/cpl-v10.html b/test-runner/src/junit/cpl-v10.html
deleted file mode 100644
index 36aa208d4a29..000000000000
--- a/test-runner/src/junit/cpl-v10.html
+++ /dev/null
@@ -1,125 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
-<HTML>
-<HEAD>
-<TITLE>Common Public License - v 1.0</TITLE>
-<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</HEAD>
-
-<BODY BGCOLOR="#FFFFFF" VLINK="#800000">
-
-
-<P ALIGN="CENTER"><B>Common Public License - v 1.0</B>
-<P><B></B><FONT SIZE="3"></FONT>
-<P><FONT SIZE="3"></FONT><FONT SIZE="2">THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.</FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"><B>1. DEFINITIONS</B></FONT>
-<P><FONT SIZE="2">"Contribution" means:</FONT>
-
-<UL><FONT SIZE="2">a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and<BR CLEAR="LEFT">
-b) in the case of each subsequent Contributor:</FONT></UL>
-
-
-<UL><FONT SIZE="2">i) changes to the Program, and</FONT></UL>
-
-
-<UL><FONT SIZE="2">ii) additions to the Program;</FONT></UL>
-
-
-<UL><FONT SIZE="2">where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. </FONT><FONT SIZE="2">A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. </FONT><FONT SIZE="2">Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. </FONT></UL>
-
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">"Contributor" means any person or entity that distributes the Program.</FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">"Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. </FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2">"Program" means the Contributions distributed in accordance with this Agreement.</FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.</FONT>
-<P><FONT SIZE="2"><B></B></FONT>
-<P><FONT SIZE="2"><B>2. GRANT OF RIGHTS</B></FONT>
-
-<UL><FONT SIZE="2"></FONT><FONT SIZE="2">a) </FONT><FONT SIZE="2">Subject to the terms of this Agreement, each Contributor hereby grants</FONT><FONT SIZE="2"> Recipient a non-exclusive, worldwide, royalty-free copyright license to</FONT><FONT SIZE="2" COLOR="#FF0000"> </FONT><FONT SIZE="2">reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.</FONT></UL>
-
-
-<UL><FONT SIZE="2"></FONT></UL>
-
-
-<UL><FONT SIZE="2"></FONT><FONT SIZE="2">b) Subject to the terms of this Agreement, each Contributor hereby grants </FONT><FONT SIZE="2">Recipient a non-exclusive, worldwide,</FONT><FONT SIZE="2" COLOR="#008000"> </FONT><FONT SIZE="2">royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. </FONT></UL>
-
-
-<UL><FONT SIZE="2"></FONT></UL>
-
-
-<UL><FONT SIZE="2">c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.</FONT></UL>
-
-
-<UL><FONT SIZE="2"></FONT></UL>
-
-
-<UL><FONT SIZE="2">d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. </FONT></UL>
-
-
-<UL><FONT SIZE="2"></FONT></UL>
-
-<P><FONT SIZE="2"><B>3. REQUIREMENTS</B></FONT>
-<P><FONT SIZE="2"><B></B>A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:</FONT>
-
-<UL><FONT SIZE="2">a) it complies with the terms and conditions of this Agreement; and</FONT></UL>
-
-
-<UL><FONT SIZE="2">b) its license agreement:</FONT></UL>
-
-
-<UL><FONT SIZE="2">i) effectively disclaims</FONT><FONT SIZE="2"> on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; </FONT></UL>
-
-
-<UL><FONT SIZE="2">ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; </FONT></UL>
-
-
-<UL><FONT SIZE="2">iii)</FONT><FONT SIZE="2"> states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and</FONT></UL>
-
-
-<UL><FONT SIZE="2">iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.</FONT><FONT SIZE="2" COLOR="#0000FF"> </FONT><FONT SIZE="2" COLOR="#FF0000"></FONT></UL>
-
-
-<UL><FONT SIZE="2" COLOR="#FF0000"></FONT><FONT SIZE="2"></FONT></UL>
-
-<P><FONT SIZE="2">When the Program is made available in source code form:</FONT>
-
-<UL><FONT SIZE="2">a) it must be made available under this Agreement; and </FONT></UL>
-
-
-<UL><FONT SIZE="2">b) a copy of this Agreement must be included with each copy of the Program. </FONT></UL>
-
-<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT>
-<P><FONT SIZE="2" COLOR="#0000FF"><STRIKE></STRIKE></FONT><FONT SIZE="2">Contributors may not remove or alter any copyright notices contained within the Program. </FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. </FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"><B>4. COMMERCIAL DISTRIBUTION</B></FONT>
-<P><FONT SIZE="2">Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.</FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.</FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2" COLOR="#0000FF"></FONT>
-<P><FONT SIZE="2" COLOR="#0000FF"></FONT><FONT SIZE="2"><B>5. NO WARRANTY</B></FONT>
-<P><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is</FONT><FONT SIZE="2"> solely responsible for determining the appropriateness of using and distributing </FONT><FONT SIZE="2">the Program</FONT><FONT SIZE="2"> and assumes all risks associated with its exercise of rights under this Agreement</FONT><FONT SIZE="2">, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, </FONT><FONT SIZE="2">programs or equipment, and unavailability or interruption of operations</FONT><FONT SIZE="2">. </FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2"><B>6. DISCLAIMER OF LIABILITY</B></FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2">EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES </FONT><FONT SIZE="2">(INCLUDING WITHOUT LIMITATION LOST PROFITS),</FONT><FONT SIZE="2"> HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"><B>7. GENERAL</B></FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2">If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.</FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. </FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. </FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2">Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to </FONT><FONT SIZE="2">publish new versions (including revisions) of this Agreement from time to </FONT><FONT SIZE="2">time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. </FONT><FONT SIZE="2">Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new </FONT><FONT SIZE="2">version. </FONT><FONT SIZE="2">Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, </FONT><FONT SIZE="2">by implication, estoppel or otherwise</FONT><FONT SIZE="2">.</FONT><FONT SIZE="2"> All rights in the Program not expressly granted under this Agreement are reserved.</FONT>
-<P><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2">This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.</FONT>
-<P><FONT SIZE="2"></FONT><FONT SIZE="2"></FONT>
-<P><FONT SIZE="2"></FONT>
-
-</BODY>
-
-</HTML> \ No newline at end of file
diff --git a/test-runner/tests/Android.bp b/test-runner/tests/Android.bp
index ac21bcb9d124..aad2bee8cb84 100644
--- a/test-runner/tests/Android.bp
+++ b/test-runner/tests/Android.bp
@@ -13,12 +13,7 @@
// limitations under the License.
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
android_test {
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 8d05a974dc40..0e0d212efcf1 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -26,6 +26,11 @@ android_test {
"platform-test-annotations",
"platform-test-rules",
"truth",
+
+ // beadstead
+ "Nene",
+ "Harrier",
+ "TestApp",
],
test_suites: [
"general-tests",
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index b66ceba458ac..867c0a6e8a02 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -23,14 +23,20 @@ import android.content.pm.PackageManager;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-@RunWith(JUnit4.class)
+@RunWith(BedsteadJUnit4.class)
public final class ConcurrentMultiUserTest {
+ @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
@Before
public void doBeforeEachTest() {
// No op
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index b5d5b5fb6d92..eca258c5a74d 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,7 +1,6 @@
{
"presubmit": [
- // TODO(b/326897452): Reenable after JDK 21 switch.
- // { "name": "tiny-framework-dump-test" },
+ { "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
{ "name": "hoststubgen-invoke-test" }
],