summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java572
-rw-r--r--config/preloaded-classes2
-rw-r--r--core/api/current.txt71
-rw-r--r--core/api/system-current.txt4
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/ActivityManager.java2
-rw-r--r--core/java/android/app/AppOpsManager.java49
-rw-r--r--core/java/android/app/ApplicationPackageManager.java3
-rw-r--r--core/java/android/app/ApplicationStartInfo.java40
-rw-r--r--core/java/android/app/background_install_control_manager.aconfig9
-rw-r--r--core/java/android/content/pm/ArchivedActivityInfo.java39
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl2
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/LauncherApps.java25
-rw-r--r--core/java/android/content/pm/PackageInstaller.java33
-rw-r--r--core/java/android/content/pm/PackageManager.java38
-rw-r--r--core/java/android/content/pm/flags.aconfig7
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/content/rollback/RollbackInfo.java106
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java107
-rw-r--r--core/java/android/os/VintfObject.java4
-rw-r--r--core/java/android/os/VintfRuntimeInfo.java4
-rw-r--r--core/java/android/permission/flags.aconfig8
-rw-r--r--core/java/android/provider/Settings.java95
-rw-r--r--core/java/android/text/Layout.java18
-rw-r--r--core/java/android/text/TextLine.java68
-rw-r--r--core/java/android/view/ViewRootImpl.java22
-rw-r--r--core/jni/Android.bp23
-rw-r--r--core/jni/AndroidRuntime.cpp4
-rw-r--r--core/jni/android_os_Debug.cpp9
-rw-r--r--core/jni/android_os_HwBinder.cpp1
-rw-r--r--core/jni/android_os_VintfObject.cpp33
-rw-r--r--core/jni/android_os_VintfRuntimeInfo.cpp17
-rw-r--r--core/jni/core_jni_helpers.h56
-rw-r--r--core/jni/jni_wrappers.h79
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/values/config.xml29
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java21
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java25
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java37
-rw-r--r--core/tests/coretests/src/android/text/TextLineJustificationTest.kt129
-rw-r--r--core/tests/coretests/src/android/text/TextLineTest.java2
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java6
-rw-r--r--data/etc/privapp-permissions-platform.xml32
-rw-r--r--graphics/java/android/graphics/text/LineBreaker.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt2
-rw-r--r--libs/hwui/AutoBackendTextureRelease.cpp6
-rw-r--r--libs/hwui/jni/PathMeasure.cpp4
-rw-r--r--location/java/android/location/flags/gnss.aconfig12
-rw-r--r--media/java/android/media/IAudioService.aidl11
-rw-r--r--media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl3
-rw-r--r--media/java/android/media/LoudnessCodecController.java (renamed from media/java/android/media/LoudnessCodecConfigurator.java)280
-rw-r--r--media/java/android/media/LoudnessCodecDispatcher.java50
-rw-r--r--media/java/android/media/MediaFormat.java49
-rw-r--r--media/java/android/media/MediaPlayer.java1
-rw-r--r--media/java/android/media/MediaRouter2.java22
-rw-r--r--media/java/android/media/audiofx/Virtualizer.java5
-rw-r--r--media/java/android/media/tv/ITvInputClient.aidl1
-rw-r--r--media/java/android/media/tv/ITvInputSessionCallback.aidl1
-rw-r--r--media/java/android/media/tv/TvInputManager.java34
-rw-r--r--media/java/android/media/tv/TvInputService.java28
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java11
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppManager.java16
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java16
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java16
-rw-r--r--media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java (renamed from media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java)167
-rw-r--r--nfc/api/current.txt18
-rw-r--r--nfc/api/system-current.txt4
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl6
-rw-r--r--nfc/java/android/nfc/INfcWlcStateListener.aidl6
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java15
-rw-r--r--nfc/java/android/nfc/NfcWlcStateListener.java12
-rw-r--r--nfc/java/android/nfc/WlcLDeviceInfo.java107
-rw-r--r--nfc/java/android/nfc/WlcListenerDeviceInfo.aidl (renamed from nfc/java/android/nfc/WlcLDeviceInfo.aidl)2
-rw-r--r--nfc/java/android/nfc/WlcListenerDeviceInfo.java145
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java106
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java100
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java19
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java6
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt53
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt6
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_0.xml49
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_1.xml49
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_2.xml47
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_not_connected.xml42
-rw-r--r--packages/SystemUI/res/layout/bindable_status_bar_icon.xml34
-rw-r--r--packages/SystemUI/res/layout/qs_customize_panel_content.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt450
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java306
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt150
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt231
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java7
-rw-r--r--packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt105
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt47
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt12
-rw-r--r--services/autofill/bugfixes.aconfig7
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java185
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java10
-rw-r--r--services/core/java/com/android/server/SystemConfig.java49
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java6
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java40
-rw-r--r--services/core/java/com/android/server/audio/LoudnessCodecHelper.java358
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricContext.java20
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java13
-rw-r--r--services/core/java/com/android/server/biometrics/log/OperationContextExt.java18
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java34
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java31
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java49
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java52
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java33
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java36
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java19
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java9
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java6
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java30
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java178
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java71
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java18
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java24
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java30
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionAllowlist.java74
-rw-r--r--services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java2
-rw-r--r--services/core/java/com/android/server/policy/window_policy_flags.aconfig2
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java10
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java2
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java13
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java17
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java23
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp62
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt80
-rw-r--r--services/tests/BackgroundInstallControlServiceTests/host/Android.bp1
-rw-r--r--services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java23
-rw-r--r--services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml2
-rw-r--r--services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java50
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java (renamed from services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java)9
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java (renamed from services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java)8
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java7
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java (renamed from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java)10
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java (renamed from services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java)9
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java (renamed from services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java)10
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java534
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java140
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java521
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java74
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java82
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java52
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java127
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java25
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl10
-rw-r--r--tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java10
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt20
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java8
291 files changed, 8074 insertions, 2244 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 44afbe6aff51..14cce19ef676 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -16,6 +16,11 @@
package com.android.server.job.controllers;
+import static android.app.job.JobInfo.PRIORITY_DEFAULT;
+import static android.app.job.JobInfo.PRIORITY_HIGH;
+import static android.app.job.JobInfo.PRIORITY_LOW;
+import static android.app.job.JobInfo.PRIORITY_MAX;
+import static android.app.job.JobInfo.PRIORITY_MIN;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -43,9 +48,12 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseArrayMap;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -91,6 +99,23 @@ public final class FlexibilityController extends StateController {
*/
private long mFallbackFlexibilityDeadlineMs =
FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ /**
+ * The default deadline that all flexible constraints should be dropped by if a job lacks
+ * a deadline, keyed by job priority.
+ */
+ private SparseLongArray mFallbackFlexibilityDeadlines =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
+ /**
+ * The scores to use for each job, keyed by job priority.
+ */
+ private SparseIntArray mFallbackFlexibilityDeadlineScores =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+ /**
+ * The amount of time to add (scaled by job run score) to the fallback flexibility deadline,
+ * keyed by job priority.
+ */
+ private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
@@ -117,10 +142,10 @@ public final class FlexibilityController extends StateController {
/**
* The percent of a job's lifecycle to drop number of required constraints.
- * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
- * the controller should have i+1 constraints dropped.
+ * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+ * the controller should have i+1 constraints dropped. Keyed by job priority.
*/
- private int[] mPercentToDropConstraints;
+ private SparseArray<int[]> mPercentsToDropConstraints;
/**
* Keeps track of what flexible constraints are satisfied at the moment.
@@ -199,6 +224,86 @@ public final class FlexibilityController extends StateController {
}
};
+ /** Helper object to track job run score for each app. */
+ private static class JobScoreTracker {
+ private static class JobScoreBucket {
+ @ElapsedRealtimeLong
+ public long startTimeElapsed;
+ public int score;
+
+ private void reset() {
+ startTimeElapsed = 0;
+ score = 0;
+ }
+ }
+
+ private static final int NUM_SCORE_BUCKETS = 24;
+ private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
+ private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
+ private int mScoreBucketIndex = 0;
+
+ public void addScore(int add, long nowElapsed) {
+ JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
+ if (bucket == null) {
+ bucket = new JobScoreBucket();
+ bucket.startTimeElapsed = nowElapsed;
+ mScoreBuckets[mScoreBucketIndex] = bucket;
+ } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
+ // The bucket is too old.
+ bucket.reset();
+ bucket.startTimeElapsed = nowElapsed;
+ } else if (bucket.startTimeElapsed
+ < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
+ // The current bucket's duration has completed. Move on to the next bucket.
+ mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS;
+ addScore(add, nowElapsed);
+ return;
+ }
+
+ bucket.score += add;
+ }
+
+ public int getScore(long nowElapsed) {
+ int score = 0;
+ final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
+ for (JobScoreBucket bucket : mScoreBuckets) {
+ if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
+ score += bucket.score;
+ }
+ }
+ return score;
+ }
+
+ public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) {
+ pw.print("{");
+
+ boolean printed = false;
+ for (int x = 0; x < mScoreBuckets.length; ++x) {
+ final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length;
+ final JobScoreBucket jsb = mScoreBuckets[idx];
+ if (jsb == null || jsb.startTimeElapsed == 0) {
+ continue;
+ }
+ if (printed) {
+ pw.print(", ");
+ }
+ TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw);
+ pw.print("=");
+ pw.print(jsb.score);
+ printed = true;
+ }
+
+ pw.print("}");
+ }
+ }
+
+ /**
+ * Set of {@link JobScoreTracker JobScoreTrackers} for each app.
+ * Keyed by source UID -> source package.
+ **/
+ private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers =
+ new SparseArrayMap<>();
+
private static final int MSG_CHECK_ALL_JOBS = 0;
/** Check the jobs in {@link #mJobsToCheck} */
private static final int MSG_CHECK_JOBS = 1;
@@ -228,8 +333,8 @@ public final class FlexibilityController extends StateController {
mFcConfig = new FcConfig();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
mContext, AppSchedulingModuleThread.get().getLooper());
- mPercentToDropConstraints =
- mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ mPercentsToDropConstraints =
+ FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
mPrefetchController = prefetchController;
if (mFlexibilityEnabled) {
@@ -272,6 +377,36 @@ public final class FlexibilityController extends StateController {
}
@Override
+ public void prepareForExecutionLocked(JobStatus jobStatus) {
+ // Use the job's requested priority to determine its score since that is what the developer
+ // selected and it will be stable across job runs.
+ final int score = mFallbackFlexibilityDeadlineScores
+ .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ JobScoreTracker jobScoreTracker =
+ mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+ if (jobScoreTracker == null) {
+ jobScoreTracker = new JobScoreTracker();
+ mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(),
+ jobScoreTracker);
+ }
+ jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis());
+ }
+
+ @Override
+ public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+ // The job didn't actually start. Undo the score increase.
+ JobScoreTracker jobScoreTracker =
+ mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+ if (jobScoreTracker == null) {
+ Slog.e(TAG, "Unprepared a job that didn't result in a score change");
+ return;
+ }
+ final int score = mFallbackFlexibilityDeadlineScores
+ .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
+ }
+
+ @Override
@GuardedBy("mLock")
public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) {
if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
@@ -286,12 +421,33 @@ public final class FlexibilityController extends StateController {
public void onAppRemovedLocked(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
mPrefetchLifeCycleStart.delete(userId, packageName);
+ mJobScoreTrackers.delete(uid, packageName);
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName))
+ || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) {
+ mJobsToCheck.removeAt(i);
+ }
+ }
}
@Override
@GuardedBy("mLock")
public void onUserRemovedLocked(int userId) {
mPrefetchLifeCycleStart.delete(userId);
+ for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) {
+ final int uid = mJobScoreTrackers.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mJobScoreTrackers.deleteAt(u);
+ }
+ }
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if (UserHandle.getUserId(js.getSourceUid()) == userId
+ || UserHandle.getUserId(js.getUid()) == userId) {
+ mJobsToCheck.removeAt(i);
+ }
+ }
}
boolean isEnabled() {
@@ -308,9 +464,9 @@ public final class FlexibilityController extends StateController {
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
// Only exclude DEFAULT+ priority jobs for BFGS+ apps
|| (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
- && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
+ && js.getEffectivePriority() >= PRIORITY_DEFAULT)
// For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs.
- || (js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT
+ || (js.getEffectivePriority() >= PRIORITY_DEFAULT
&& mPowerAllowlistedApps.contains(js.getSourcePackageName()))
|| hasEnoughSatisfiedConstraintsLocked(js)
|| mService.isCurrentlyRunningLocked(js);
@@ -462,7 +618,14 @@ public final class FlexibilityController extends StateController {
@VisibleForTesting
@GuardedBy("mLock")
- long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+ int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) {
+ final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName);
+ return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed);
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) {
if (js.getJob().isPrefetch()) {
final long estimatedLaunchTime =
mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
@@ -486,15 +649,28 @@ public final class FlexibilityController extends StateController {
(long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
mMaxRescheduledDeadline);
}
- return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
- ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
+ if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
+ // Intentionally use the effective priority here. If a job's priority was effectively
+ // lowered, it will be less likely to run quickly given other policies in JobScheduler.
+ // Thus, there's no need to further delay the job based on flex policy.
+ final int jobPriority = js.getEffectivePriority();
+ final int jobScore =
+ getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
+ // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
+ final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
+ mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
+ + mFallbackFlexibilityAdditionalScoreTimeFactors
+ .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
+ return earliest + fallbackDeadlineMs;
+ }
+ return js.getLatestRunTimeElapsed();
}
@VisibleForTesting
@GuardedBy("mLock")
int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
return 0;
}
@@ -510,7 +686,8 @@ public final class FlexibilityController extends StateController {
@GuardedBy("mLock")
long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest =
+ getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest);
return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
}
@@ -518,15 +695,27 @@ public final class FlexibilityController extends StateController {
@ElapsedRealtimeLong
@GuardedBy("mLock")
long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
if (latest == NO_LIFECYCLE_END
- || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
+ || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) {
return NO_LIFECYCLE_END;
}
- final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
+ final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()];
final long percentInTime = ((latest - earliest) * percent) / 100;
return earliest + percentInTime;
}
+ @NonNull
+ private int[] getPercentsToDropConstraints(int priority) {
+ int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority);
+ if (percentsToDropConstraints == null) {
+ Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority));
+ return new int[]{50, 60, 70, 80};
+ }
+ return percentsToDropConstraints;
+ }
+
@Override
@GuardedBy("mLock")
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
@@ -681,10 +870,12 @@ public final class FlexibilityController extends StateController {
Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
int toDrop = 0;
for (int i = 0; i < numAppliedConstraints; i++) {
- if (curPercent >= mPercentToDropConstraints[i]) {
+ if (curPercent >= percentsToDropConstraints[i]) {
toDrop++;
}
}
@@ -705,8 +896,10 @@ public final class FlexibilityController extends StateController {
final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
int toDrop = 0;
final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
- if (curPercent >= mPercentToDropConstraints[i]) {
+ if (curPercent >= percentsToDropConstraints[i]) {
toDrop++;
}
}
@@ -733,7 +926,7 @@ public final class FlexibilityController extends StateController {
return mTrackedJobs.size();
}
- public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+ public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
for (int j = 0; j < jobs.size(); j++) {
@@ -744,8 +937,18 @@ public final class FlexibilityController extends StateController {
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(" Num Required Constraints: ");
+ pw.print("-> Num Required Constraints: ");
pw.print(js.getNumRequiredFlexibleConstraints());
+
+ pw.print(", lifecycle=[");
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ pw.print(earliest);
+ pw.print(", (");
+ pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed));
+ pw.print("%), ");
+ pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest));
+ pw.print("]");
+
pw.println();
}
}
@@ -768,7 +971,7 @@ public final class FlexibilityController extends StateController {
public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
synchronized (mLock) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
final long nextTimeElapsed =
getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
@@ -936,10 +1139,16 @@ public final class FlexibilityController extends StateController {
FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadlines";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors";
static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms";
- static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
+ static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+ FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints";
static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
@@ -952,9 +1161,50 @@ public final class FlexibilityController extends StateController {
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
- private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
+ static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+ static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+ new SparseIntArray();
+ static final SparseLongArray
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ new SparseLongArray();
@VisibleForTesting
- final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+ static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+ new SparseArray<>();
+
+ static {
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_MAX, 0);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_MAX, new int[]{1, 2, 3, 4});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_LOW, new int[]{50, 60, 70, 80});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_MIN, new int[]{55, 65, 75, 85});
+ }
+
+ private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
@VisibleForTesting
@@ -968,9 +1218,11 @@ public final class FlexibilityController extends StateController {
public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
- /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
- public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ /**
+ * The percentages of a jobs' lifecycle to drop the number of required constraints.
+ * Keyed by job priority.
+ */
+ public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>();
/** Initial fallback flexible deadline for rescheduled jobs. */
public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
/** The max deadline for rescheduled jobs. */
@@ -980,10 +1232,56 @@ public final class FlexibilityController extends StateController {
* it in order to run jobs.
*/
public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+ /**
+ * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in
+ * milliseconds and keyed by job priority.
+ */
+ public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+ /**
+ * The score to ascribe to each job, keyed by job priority.
+ */
+ public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray();
+ /**
+ * How much additional time to increase the fallback deadline by based on the app's current
+ * job run score. Values are in
+ * milliseconds and keyed by job priority.
+ */
+ public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ new SparseLongArray();
+
+ FcConfig() {
+ // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying
+ // the DEFAULT_* data structures in other parts of the code.
+ for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINES.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i));
+ }
+ for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i));
+ }
+ for (int i = 0;
+ i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size();
+ ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .valueAt(i));
+ }
+ for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) {
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put(
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i),
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i));
+ }
+ }
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
+ // TODO(257322915): add appropriate minimums and maximums to constants when parsing
switch (key) {
case KEY_APPLIED_CONSTRAINTS:
APPLIED_CONSTRAINTS =
@@ -1034,6 +1332,33 @@ public final class FlexibilityController extends StateController {
properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINES:
+ if (parsePriorityToLongKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINES,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) {
+ mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES:
+ if (parsePriorityToIntKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) {
+ mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS:
+ if (parsePriorityToLongKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) {
+ mFallbackFlexibilityAdditionalScoreTimeFactors =
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
mShouldReevaluateConstraints = true;
}
break;
@@ -1056,25 +1381,69 @@ public final class FlexibilityController extends StateController {
mShouldReevaluateConstraints = true;
}
break;
- case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
- String dropPercentString = properties.getString(key, "");
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- parsePercentToDropString(dropPercentString);
- if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
- && !Arrays.equals(mPercentToDropConstraints,
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
- mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+ case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS:
+ if (parsePercentToDropKeyValueString(
+ properties.getString(key, null),
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) {
+ mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
mShouldReevaluateConstraints = true;
}
break;
}
}
- private int[] parsePercentToDropString(String s) {
- String[] dropPercentString = s.split(",");
+ private boolean parsePercentToDropKeyValueString(@Nullable String s,
+ SparseArray<int[]> into, SparseArray<int[]> defaults) {
+ final KeyValueListParser priorityParser = new KeyValueListParser(',');
+ try {
+ priorityParser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad percent to drop key value string given", e);
+ // Clear the string and continue with the defaults.
+ priorityParser.setString(null);
+ }
+
+ final int[] oldMax = into.get(PRIORITY_MAX);
+ final int[] oldHigh = into.get(PRIORITY_HIGH);
+ final int[] oldDefault = into.get(PRIORITY_DEFAULT);
+ final int[] oldLow = into.get(PRIORITY_LOW);
+ final int[] oldMin = into.get(PRIORITY_MIN);
+
+ final int[] newMax = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_MAX), null));
+ final int[] newHigh = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_HIGH), null));
+ final int[] newDefault = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_DEFAULT), null));
+ final int[] newLow = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_LOW), null));
+ final int[] newMin = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_MIN), null));
+
+ into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax);
+ into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh);
+ into.put(PRIORITY_DEFAULT,
+ newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault);
+ into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow);
+ into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin);
+
+ return !Arrays.equals(oldMax, into.get(PRIORITY_MAX))
+ || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH))
+ || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT))
+ || !Arrays.equals(oldLow, into.get(PRIORITY_LOW))
+ || !Arrays.equals(oldMin, into.get(PRIORITY_MIN));
+ }
+
+ @Nullable
+ private int[] parsePercentToDropString(@Nullable String s) {
+ if (s == null || s.isEmpty()) {
+ return null;
+ }
+ final String[] dropPercentString = s.split("\\|");
int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
if (dropPercentInt.length != dropPercentString.length) {
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
}
int prevPercent = 0;
for (int i = 0; i < dropPercentString.length; i++) {
@@ -1083,11 +1452,15 @@ public final class FlexibilityController extends StateController {
Integer.parseInt(dropPercentString[i]);
} catch (NumberFormatException ex) {
Slog.e(TAG, "Provided string was improperly formatted.", ex);
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
}
if (dropPercentInt[i] < prevPercent) {
Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
+ }
+ if (dropPercentInt[i] > 100) {
+ Slog.e(TAG, "Found % over 100");
+ return null;
}
prevPercent = dropPercentInt[i];
}
@@ -1095,6 +1468,102 @@ public final class FlexibilityController extends StateController {
return dropPercentInt;
}
+ /**
+ * Parses the input string, expecting it to a key-value string where the keys are job
+ * priorities, and replaces everything in {@code into} with the values from the string,
+ * or the default values if the string contains none.
+ *
+ * Returns true if any values changed.
+ */
+ private boolean parsePriorityToIntKeyValueString(@Nullable String s,
+ SparseIntArray into, SparseIntArray defaults) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string given", e);
+ // Clear the string and continue with the defaults.
+ parser.setString(null);
+ }
+
+ final int oldMax = into.get(PRIORITY_MAX);
+ final int oldHigh = into.get(PRIORITY_HIGH);
+ final int oldDefault = into.get(PRIORITY_DEFAULT);
+ final int oldLow = into.get(PRIORITY_LOW);
+ final int oldMin = into.get(PRIORITY_MIN);
+
+ final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX),
+ defaults.get(PRIORITY_MAX));
+ final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH),
+ defaults.get(PRIORITY_HIGH));
+ final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT),
+ defaults.get(PRIORITY_DEFAULT));
+ final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW),
+ defaults.get(PRIORITY_LOW));
+ final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN),
+ defaults.get(PRIORITY_MIN));
+
+ into.put(PRIORITY_MAX, newMax);
+ into.put(PRIORITY_HIGH, newHigh);
+ into.put(PRIORITY_DEFAULT, newDefault);
+ into.put(PRIORITY_LOW, newLow);
+ into.put(PRIORITY_MIN, newMin);
+
+ return oldMax != newMax
+ || oldHigh != newHigh
+ || oldDefault != newDefault
+ || oldLow != newLow
+ || oldMin != newMin;
+ }
+
+ /**
+ * Parses the input string, expecting it to a key-value string where the keys are job
+ * priorities, and replaces everything in {@code into} with the values from the string,
+ * or the default values if the string contains none.
+ *
+ * Returns true if any values changed.
+ */
+ private boolean parsePriorityToLongKeyValueString(@Nullable String s,
+ SparseLongArray into, SparseLongArray defaults) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string given", e);
+ // Clear the string and continue with the defaults.
+ parser.setString(null);
+ }
+
+ final long oldMax = into.get(PRIORITY_MAX);
+ final long oldHigh = into.get(PRIORITY_HIGH);
+ final long oldDefault = into.get(PRIORITY_DEFAULT);
+ final long oldLow = into.get(PRIORITY_LOW);
+ final long oldMin = into.get(PRIORITY_MIN);
+
+ final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX),
+ defaults.get(PRIORITY_MAX));
+ final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH),
+ defaults.get(PRIORITY_HIGH));
+ final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT),
+ defaults.get(PRIORITY_DEFAULT));
+ final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW),
+ defaults.get(PRIORITY_LOW));
+ final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN),
+ defaults.get(PRIORITY_MIN));
+
+ into.put(PRIORITY_MAX, newMax);
+ into.put(PRIORITY_HIGH, newHigh);
+ into.put(PRIORITY_DEFAULT, newDefault);
+ into.put(PRIORITY_LOW, newLow);
+ into.put(PRIORITY_MIN, newMin);
+
+ return oldMax != newMax
+ || oldHigh != newHigh
+ || oldDefault != newDefault
+ || oldLow != newLow
+ || oldMin != newMin;
+ }
+
private void dump(IndentingPrintWriter pw) {
pw.println();
pw.print(FlexibilityController.class.getSimpleName());
@@ -1111,10 +1580,15 @@ public final class FlexibilityController extends StateController {
pw.println(")");
pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println();
pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println();
- pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println();
+ pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println();
pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS)
@@ -1171,7 +1645,21 @@ public final class FlexibilityController extends StateController {
pw.println(mPowerAllowlistedApps);
pw.println();
- mFlexibilityTracker.dump(pw, predicate);
+ mFlexibilityTracker.dump(pw, predicate, nowElapsed);
+
+ pw.println();
+ pw.println("Job scores:");
+ pw.increaseIndent();
+ mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> {
+ pw.print(uid);
+ pw.print("/");
+ pw.print(pkgName);
+ pw.print(": ");
+ jobScoreTracker.dump(pw, nowElapsed);
+ pw.println();
+ });
+ pw.decreaseIndent();
+
pw.println();
mFlexibilityAlarmQueue.dump(pw);
}
diff --git a/config/preloaded-classes b/config/preloaded-classes
index c49971eb68ae..11b24f53ceaf 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6172,8 +6172,6 @@ android.os.VibratorInfo$FrequencyProfile$1
android.os.VibratorInfo$FrequencyProfile
android.os.VibratorInfo
android.os.VibratorManager
-android.os.VintfObject
-android.os.VintfRuntimeInfo
android.os.WorkSource$1
android.os.WorkSource$WorkChain$1
android.os.WorkSource$WorkChain
diff --git a/core/api/current.txt b/core/api/current.txt
index 4e61b0aaac62..109170615c94 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -195,6 +195,7 @@ package android {
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS";
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS";
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES";
+ field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING";
field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER";
@@ -12318,6 +12319,7 @@ package android.content.pm {
method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
+ method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -17875,6 +17877,7 @@ package android.graphics.text {
field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
@@ -22153,16 +22156,17 @@ package android.media {
method public void onJetUserIdUpdate(android.media.JetPlayer, int, int);
}
- @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator {
+ @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create();
- method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec);
+ method public void close();
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release();
method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") public void setAudioTrack(@Nullable android.media.AudioTrack);
}
- @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener {
+ @FlaggedApi("android.media.audio.loudness_configurator_api") public static interface LoudnessCodecController.OnLoudnessCodecUpdateListener {
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public default android.os.Bundle onLoudnessCodecUpdate(@NonNull android.media.MediaCodec, @NonNull android.os.Bundle);
}
@@ -23316,6 +23320,8 @@ package android.media {
field public static final String KEY_AUDIO_SESSION_ID = "audio-session-id";
field public static final String KEY_BITRATE_MODE = "bitrate-mode";
field public static final String KEY_BIT_RATE = "bitrate";
+ field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+ field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE = "buffer-batch-threshold-output-size";
field public static final String KEY_CAPTION_SERVICE_NUMBER = "caption-service-number";
field public static final String KEY_CAPTURE_RATE = "capture-rate";
field public static final String KEY_CHANNEL_COUNT = "channel-count";
@@ -25472,34 +25478,34 @@ package android.media.audiofx {
field public short preset;
}
- public class Virtualizer extends android.media.audiofx.AudioEffect {
- ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
- method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public boolean getStrengthSupported();
- method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
- method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
- field public static final int PARAM_STRENGTH = 1; // 0x1
- field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
- field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
- field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
- field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
- field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
+ @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect {
+ ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public boolean getStrengthSupported();
+ method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
+ method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+ field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1
+ field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
+ field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
+ field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
+ field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
+ field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
}
- public static interface Virtualizer.OnParameterChangeListener {
- method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
+ @Deprecated public static interface Virtualizer.OnParameterChangeListener {
+ method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
}
- public static class Virtualizer.Settings {
- ctor public Virtualizer.Settings();
- ctor public Virtualizer.Settings(String);
- field public short strength;
+ @Deprecated public static class Virtualizer.Settings {
+ ctor @Deprecated public Virtualizer.Settings();
+ ctor @Deprecated public Virtualizer.Settings(String);
+ field @Deprecated public short strength;
}
public class Visualizer {
@@ -42218,9 +42224,11 @@ package android.telecom {
method public android.graphics.drawable.Icon getIcon();
method public CharSequence getLabel();
method public CharSequence getShortDescription();
+ method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public java.util.Set<android.telecom.PhoneAccountHandle> getSimultaneousCallingRestriction();
method public android.net.Uri getSubscriptionAddress();
method public java.util.List<java.lang.String> getSupportedUriSchemes();
method public boolean hasCapabilities(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public boolean hasSimultaneousCallingRestriction();
method public boolean isEnabled();
method public boolean supportsUriScheme(String);
method public android.telecom.PhoneAccount.Builder toBuilder();
@@ -42261,12 +42269,14 @@ package android.telecom {
ctor public PhoneAccount.Builder(android.telecom.PhoneAccount);
method public android.telecom.PhoneAccount.Builder addSupportedUriScheme(String);
method public android.telecom.PhoneAccount build();
+ method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder clearSimultaneousCallingRestriction();
method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri);
method public android.telecom.PhoneAccount.Builder setCapabilities(int);
method public android.telecom.PhoneAccount.Builder setExtras(android.os.Bundle);
method public android.telecom.PhoneAccount.Builder setHighlightColor(int);
method public android.telecom.PhoneAccount.Builder setIcon(android.graphics.drawable.Icon);
method public android.telecom.PhoneAccount.Builder setShortDescription(CharSequence);
+ method @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @NonNull public android.telecom.PhoneAccount.Builder setSimultaneousCallingRestriction(@NonNull java.util.Set<android.telecom.PhoneAccountHandle>);
method public android.telecom.PhoneAccount.Builder setSubscriptionAddress(android.net.Uri);
method public android.telecom.PhoneAccount.Builder setSupportedUriSchemes(java.util.List<java.lang.String>);
}
@@ -46947,6 +46957,7 @@ package android.text {
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index debf1bfdfc8c..8a9f1c40bd43 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3959,6 +3959,7 @@ package android.content.pm {
method public void setInstallAsInstantApp(boolean);
method public void setInstallAsVirtualPreload();
method public void setRequestDowngrade(boolean);
+ method @FlaggedApi("android.content.pm.recoverability_detection") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackImpactLevel(int);
method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
@@ -4127,6 +4128,9 @@ package android.content.pm {
field public static final int ROLLBACK_DATA_POLICY_RESTORE = 0; // 0x0
field public static final int ROLLBACK_DATA_POLICY_RETAIN = 2; // 0x2
field public static final int ROLLBACK_DATA_POLICY_WIPE = 1; // 0x1
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_HIGH = 1; // 0x1
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_LOW = 0; // 0x0
+ field @FlaggedApi("android.content.pm.recoverability_detection") public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2; // 0x2
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN = 0; // 0x0
field public static final int SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE = 1; // 0x1
field public static final int SYSTEM_APP_STATE_INSTALLED = 2; // 0x2
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a866a34166f7..e8a4a829fd50 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -156,6 +156,7 @@ package android.app {
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20
+ field public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5; // 0x5
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
field public static final int PROCESS_STATE_TOP = 2; // 0x2
field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -1209,6 +1210,10 @@ package android.content.res {
package android.content.rollback {
+ public final class RollbackInfo implements android.os.Parcelable {
+ method @FlaggedApi("android.content.pm.recoverability_detection") public int getRollbackImpactLevel();
+ }
+
public final class RollbackManager {
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long);
method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b2c64756e4bf..9d20f3c47bb5 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -712,6 +712,8 @@ public class ActivityManager {
/** @hide Process is hosting a foreground service due to a system binding. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE =
ProcessStateEnum.BOUND_FOREGROUND_SERVICE;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4b2e93fda171..ea37e7fbcce1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1533,10 +1533,11 @@ public class AppOpsManager {
public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
/**
- * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+ * Rapid clearing of notifications by a notification listener
*
* @hide
*/
+ // See b/289080543 for more details
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
@@ -1547,9 +1548,24 @@ public class AppOpsManager {
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ /**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final int OP_UNARCHIVAL_CONFIRMATION =
+ AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 144;
+ public static final int _NUM_OP = 146;
/**
* All app ops represented as strings.
@@ -1699,6 +1715,8 @@ public class AppOpsManager {
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ OPSTR_ARCHIVE_ICON_OVERLAY,
+ OPSTR_UNARCHIVAL_CONFIRMATION,
})
public @interface AppOpString {}
@@ -2039,6 +2057,20 @@ public class AppOpsManager {
public static final String OPSTR_MEDIA_ROUTING_CONTROL = "android:media_routing_control";
/**
+ * Whether the app has enabled to receive the icon overlay for fetching archived apps.
+ *
+ * @hide
+ */
+ public static final String OPSTR_ARCHIVE_ICON_OVERLAY = "android:archive_icon_overlay";
+
+ /**
+ * Whether the app has enabled compatibility support for unarchival.
+ *
+ * @hide
+ */
+ public static final String OPSTR_UNARCHIVAL_CONFIRMATION = "android:unarchival_support";
+
+ /**
* AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage}
*
* <p>MediaProvider is the only component (outside of system server) that should care about this
@@ -2373,10 +2405,11 @@ public class AppOpsManager {
"android:reserved_for_testing";
/**
- * Rapid clearing of notifications by a notification listener, see b/289080543 for details
+ * Rapid clearing of notifications by a notification listener
*
* @hide
*/
+ // See b/289080543 for more details
@SystemApi
@FlaggedApi(FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED)
public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
@@ -2502,6 +2535,8 @@ public class AppOpsManager {
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OP_ARCHIVE_ICON_OVERLAY,
+ OP_UNARCHIVAL_CONFIRMATION,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2958,6 +2993,12 @@ public class AppOpsManager {
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
.setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
+ new AppOpInfo.Builder(OP_ARCHIVE_ICON_OVERLAY, OPSTR_ARCHIVE_ICON_OVERLAY,
+ "ARCHIVE_ICON_OVERLAY")
+ .setDefaultMode(MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION,
+ "UNARCHIVAL_CONFIRMATION")
+ .setDefaultMode(MODE_ALLOWED).build(),
};
// The number of longs needed to form a full bitmask of app ops
@@ -3092,7 +3133,7 @@ public class AppOpsManager {
/**
* Retrieve the permission associated with an operation, or null if there is not one.
- *
+
* @param op The operation name.
*
* @hide
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 34c44f9489d5..4f1db7d3784a 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -4032,7 +4032,8 @@ public class ApplicationPackageManager extends PackageManager {
private Drawable getArchivedAppIcon(String packageName) {
try {
return new BitmapDrawable(null,
- mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
+ mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()),
+ mContext.getPackageName()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index afa513dbaaef..c6712c044539 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -413,7 +413,9 @@ public final class ApplicationStartInfo implements Parcelable {
* @hide
*/
public void setIntent(Intent startIntent) {
- mStartIntent = startIntent;
+ if (startIntent != null) {
+ mStartIntent = startIntent.maybeStripForHistory();
+ }
}
/**
@@ -548,6 +550,8 @@ public final class ApplicationStartInfo implements Parcelable {
/**
* The intent used to launch the application.
*
+ * <p class="note"> Note: Intent is stripped and does not include extras.</p>
+ *
* <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
*/
@SuppressLint("IntentBuilderName")
@@ -662,6 +666,7 @@ public final class ApplicationStartInfo implements Parcelable {
private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_INTENT = "intent";
/**
* Write to a protocol buffer output stream. Protocol buffer message definition at {@link
@@ -702,10 +707,17 @@ public final class ApplicationStartInfo implements Parcelable {
}
proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
if (mStartIntent != null) {
- Parcel parcel = Parcel.obtain();
- mStartIntent.writeToParcel(parcel, 0);
- proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall());
- parcel.recycle();
+ ByteArrayOutputStream intentBytes = new ByteArrayOutputStream();
+ ObjectOutputStream intentOut = new ObjectOutputStream(intentBytes);
+ TypedXmlSerializer serializer = Xml.resolveSerializer(intentOut);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent.saveToXml(serializer);
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ serializer.endDocument();
+ proto.write(ApplicationStartInfoProto.START_INTENT,
+ intentBytes.toByteArray());
+ intentOut.close();
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
proto.end(token);
@@ -772,15 +784,17 @@ public final class ApplicationStartInfo implements Parcelable {
mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
break;
case (int) ApplicationStartInfoProto.START_INTENT:
- byte[] startIntentBytes = proto.readBytes(
- ApplicationStartInfoProto.START_INTENT);
- if (startIntentBytes.length > 0) {
- Parcel parcel = Parcel.obtain();
- parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length);
- parcel.setDataPosition(0);
- mStartIntent = Intent.CREATOR.createFromParcel(parcel);
- parcel.recycle();
+ ByteArrayInputStream intentBytes = new ByteArrayInputStream(proto.readBytes(
+ ApplicationStartInfoProto.START_INTENT));
+ ObjectInputStream intentIn = new ObjectInputStream(intentBytes);
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(intentIn);
+ XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_INTENT);
+ mStartIntent = Intent.restoreFromXml(parser);
+ } catch (XmlPullParserException e) {
+ // Intent lost
}
+ intentIn.close();
break;
case (int) ApplicationStartInfoProto.LAUNCH_MODE:
mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig
new file mode 100644
index 000000000000..029b93ab4534
--- /dev/null
+++ b/core/java/android/app/background_install_control_manager.aconfig
@@ -0,0 +1,9 @@
+package: "android.app"
+
+flag {
+ namespace: "background_install_control"
+ name: "bic_client"
+ description: "System API for background install control."
+ is_fixed_read_only: true
+ bug: "287507984"
+}
diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java
index 1faa4373d88f..166d26503625 100644
--- a/core/java/android/content/pm/ArchivedActivityInfo.java
+++ b/core/java/android/content/pm/ArchivedActivityInfo.java
@@ -91,26 +91,31 @@ public final class ArchivedActivityInfo {
* @hide
*/
public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) {
- if (drawable instanceof BitmapDrawable) {
- return ((BitmapDrawable) drawable).getBitmap();
-
- }
-
Bitmap bitmap;
- if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
- // Needed for drawables that are just a single color.
- bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ if (drawable instanceof BitmapDrawable) {
+ bitmap = ((BitmapDrawable) drawable).getBitmap();
} else {
- bitmap =
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ // Needed for drawables that are just a single color.
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ } else {
+ bitmap =
Bitmap.createBitmap(
- drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
}
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
+ if (iconSize <= 0) {
+ return bitmap;
+ }
+
+ if (bitmap.getWidth() < iconSize || bitmap.getHeight() < iconSize
+ || bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
if (scaledBitmap != bitmap) {
bitmap.recycle();
@@ -235,7 +240,7 @@ public final class ArchivedActivityInfo {
}
@DataClass.Generated(
- time = 1698789991876L,
+ time = 1705615445673L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java",
inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)")
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index a97de6368b8c..62db65f15df3 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -128,4 +128,6 @@ interface ILauncherApps {
/** Unregister a callback, so that it won't be called when LauncherApps dumps. */
void unRegisterDumpCallback(IDumpCallback cb);
+
+ void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6dc8d4738c87..380de965b143 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -840,7 +840,7 @@ interface IPackageManager {
ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
- Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
+ Bitmap getArchivedAppIcon(String packageName, in UserHandle user, String callingPackageName);
boolean isAppArchivable(String packageName, in UserHandle user);
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 1d2b1aff46bc..50be983ec938 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1801,6 +1801,31 @@ public class LauncherApps {
}
}
+ /**
+ * Enable or disable different archive compatibility options of the launcher.
+ *
+ * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
+ * that a certain app is archived. True by default.
+ * Launchers might want to disable this operation if they want to provide custom user experience
+ * to differentiate archived apps.
+ * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
+ * they click an archived app, which explains that the app will be downloaded and restored in
+ * the background. True by default.
+ * Launchers might want to disable this operation if they provide sufficient, alternative user
+ * guidance to highlight that an unarchival is starting and ongoing once an archived app is
+ * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ try {
+ mService.setArchiveCompatibilityOptions(enableIconOverlay,
+ enableUnarchivalConfirmation);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
/** @return position in mCallbacks for callback or -1 if not present. */
private int findCallbackLocked(Callback callback) {
if (callback == null) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f0efed97d8ed..22926febfb2c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2693,6 +2693,8 @@ public class PackageInstaller {
/** @hide */
public long rollbackLifetimeMillis = 0;
/** {@hide} */
+ public int rollbackImpactLevel = PackageManager.ROLLBACK_USER_IMPACT_LOW;
+ /** {@hide} */
public boolean forceQueryableOverride;
/** {@hide} */
public int requireUserAction = USER_ACTION_UNSPECIFIED;
@@ -2749,6 +2751,7 @@ public class PackageInstaller {
}
rollbackDataPolicy = source.readInt();
rollbackLifetimeMillis = source.readLong();
+ rollbackImpactLevel = source.readInt();
requireUserAction = source.readInt();
packageSource = source.readInt();
applicationEnabledSettingPersistent = source.readBoolean();
@@ -2783,6 +2786,7 @@ public class PackageInstaller {
ret.dataLoaderParams = dataLoaderParams;
ret.rollbackDataPolicy = rollbackDataPolicy;
ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
+ ret.rollbackImpactLevel = rollbackImpactLevel;
ret.requireUserAction = requireUserAction;
ret.packageSource = packageSource;
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -3121,6 +3125,28 @@ public class PackageInstaller {
}
/**
+ * rollbackImpactLevel is a measure of impact a rollback has on the user. This can take one
+ * of 3 values:
+ * <ul>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_LOW} (default)</li>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_HIGH} (1)</li>
+ * <li>{@link PackageManager#ROLLBACK_USER_IMPACT_ONLY_MANUAL} (2)</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+ @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+ public void setRollbackImpactLevel(@PackageManager.RollbackImpactLevel int impactLevel) {
+ if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackImpactLevel when rollback is not enabled");
+ }
+ rollbackImpactLevel = impactLevel;
+ }
+
+ /**
* @deprecated use {@link #setRequestDowngrade(boolean)}.
* {@hide}
*/
@@ -3493,6 +3519,7 @@ public class PackageInstaller {
pw.printPair("dataLoaderParams", dataLoaderParams);
pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
+ pw.printPair("rollbackImpactLevel", rollbackImpactLevel);
pw.printPair("applicationEnabledSettingPersistent",
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3536,6 +3563,7 @@ public class PackageInstaller {
}
dest.writeInt(rollbackDataPolicy);
dest.writeLong(rollbackLifetimeMillis);
+ dest.writeInt(rollbackImpactLevel);
dest.writeInt(requireUserAction);
dest.writeInt(packageSource);
dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3734,6 +3762,9 @@ public class PackageInstaller {
public long rollbackLifetimeMillis;
/** {@hide} */
+ public int rollbackImpactLevel;
+
+ /** {@hide} */
public int requireUserAction;
/** {@hide} */
@@ -3801,6 +3832,7 @@ public class PackageInstaller {
isPreapprovalRequested = source.readBoolean();
rollbackDataPolicy = source.readInt();
rollbackLifetimeMillis = source.readLong();
+ rollbackImpactLevel = source.readInt();
createdMillis = source.readLong();
requireUserAction = source.readInt();
installerUid = source.readInt();
@@ -4438,6 +4470,7 @@ public class PackageInstaller {
dest.writeBoolean(isPreapprovalRequested);
dest.writeInt(rollbackDataPolicy);
dest.writeLong(rollbackLifetimeMillis);
+ dest.writeInt(rollbackImpactLevel);
dest.writeLong(createdMillis);
dest.writeInt(requireUserAction);
dest.writeInt(installerUid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index aabbe698703c..4724e866f094 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1501,6 +1501,44 @@ public abstract class PackageManager {
public static final int ROLLBACK_DATA_POLICY_RETAIN = 2;
/** @hide */
+ @IntDef(prefix = {"ROLLBACK_USER_IMPACT_"}, value = {
+ ROLLBACK_USER_IMPACT_LOW,
+ ROLLBACK_USER_IMPACT_HIGH,
+ ROLLBACK_USER_IMPACT_ONLY_MANUAL,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RollbackImpactLevel {}
+
+ /**
+ * Rollback will be performed automatically in response to native crashes on startup or
+ * persistent service crashes. More suitable for apps that do not store any user data.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_LOW = 0;
+
+ /**
+ * Rollback will be performed automatically only when the device is found to be unrecoverable.
+ * More suitable for apps that store user data and have higher impact on user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_HIGH = 1;
+
+ /**
+ * Rollback will not be performed automatically. It can be triggered externally.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_RECOVERABILITY_DETECTION)
+ public static final int ROLLBACK_USER_IMPACT_ONLY_MANUAL = 2;
+
+ /** @hide */
@IntDef(flag = true, prefix = { "INSTALL_" }, value = {
INSTALL_REPLACE_EXISTING,
INSTALL_ALLOW_TEST,
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index a2cd3e153b3e..e4e9fbaf2c55 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -146,3 +146,10 @@ flag {
bug: "281848623"
}
+flag {
+ name: "recoverability_detection"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party."
+ bug: "291135724"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 7c5d3054c945..5bfc012844f8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -77,4 +77,11 @@ flag {
namespace: "profile_experiences"
description: "Move the quiet mode operations, happening on a background thread today, to a separate thread."
bug: "320483504"
+}
+
+flag {
+ name: "enable_private_space_autolock_on_restarts"
+ namespace: "profile_experiences"
+ description: "Enable auto-locking private space on device restarts"
+ bug: "296993385"
} \ No newline at end of file
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index a363718a8b1d..d128055fec6d 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -16,8 +16,12 @@
package android.content.rollback;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.Flags;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,17 +29,14 @@ import android.os.Parcelable;
import java.util.List;
/**
- * Information about a set of packages that can be, or already have been
- * rolled back together.
+ * Information about a set of packages that can be, or already have been rolled back together.
*
* @hide
*/
@SystemApi
public final class RollbackInfo implements Parcelable {
- /**
- * A unique identifier for the rollback.
- */
+ /** A unique identifier for the rollback. */
private final int mRollbackId;
private final List<PackageRollbackInfo> mPackages;
@@ -44,15 +45,39 @@ public final class RollbackInfo implements Parcelable {
private final boolean mIsStaged;
private int mCommittedSessionId;
+ private int mRollbackImpactLevel;
/** @hide */
- public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages, boolean isStaged,
- List<VersionedPackage> causePackages, int committedSessionId) {
+ public RollbackInfo(
+ int rollbackId,
+ List<PackageRollbackInfo> packages,
+ boolean isStaged,
+ List<VersionedPackage> causePackages,
+ int committedSessionId,
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
this.mRollbackId = rollbackId;
this.mPackages = packages;
this.mIsStaged = isStaged;
this.mCausePackages = causePackages;
this.mCommittedSessionId = committedSessionId;
+ this.mRollbackImpactLevel = rollbackImpactLevel;
+ }
+
+ /** @hide */
+ public RollbackInfo(
+ int rollbackId,
+ List<PackageRollbackInfo> packages,
+ boolean isStaged,
+ List<VersionedPackage> causePackages,
+ int committedSessionId) {
+ // If impact level is not set default to 0
+ this(
+ rollbackId,
+ packages,
+ isStaged,
+ causePackages,
+ committedSessionId,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
}
private RollbackInfo(Parcel in) {
@@ -61,34 +86,28 @@ public final class RollbackInfo implements Parcelable {
mIsStaged = in.readBoolean();
mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
mCommittedSessionId = in.readInt();
+ mRollbackImpactLevel = in.readInt();
}
- /**
- * Returns a unique identifier for this rollback.
- */
+ /** Returns a unique identifier for this rollback. */
public int getRollbackId() {
return mRollbackId;
}
- /**
- * Returns the list of package that are rolled back.
- */
+ /** Returns the list of package that are rolled back. */
@NonNull
public List<PackageRollbackInfo> getPackages() {
return mPackages;
}
- /**
- * Returns true if this rollback requires reboot to take effect after
- * being committed.
- */
+ /** Returns true if this rollback requires reboot to take effect after being committed. */
public boolean isStaged() {
return mIsStaged;
}
/**
- * Returns the session ID for the committed rollback for staged rollbacks.
- * Only applicable for rollbacks that have been committed.
+ * Returns the session ID for the committed rollback for staged rollbacks. Only applicable for
+ * rollbacks that have been committed.
*/
public int getCommittedSessionId() {
return mCommittedSessionId;
@@ -96,6 +115,7 @@ public final class RollbackInfo implements Parcelable {
/**
* Sets the session ID for the committed rollback for staged rollbacks.
+ *
* @hide
*/
public void setCommittedSessionId(int sessionId) {
@@ -103,15 +123,40 @@ public final class RollbackInfo implements Parcelable {
}
/**
- * Gets the list of package versions that motivated this rollback.
- * As provided to {@link #commitRollback} when the rollback was committed.
- * This is only applicable for rollbacks that have been committed.
+ * Gets the list of package versions that motivated this rollback. As provided to {@link
+ * #commitRollback} when the rollback was committed. This is only applicable for rollbacks that
+ * have been committed.
*/
@NonNull
public List<VersionedPackage> getCausePackages() {
return mCausePackages;
}
+ /**
+ * Get rollback impact level. Refer {@link
+ * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+ * on impact level.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+ public @PackageManager.RollbackImpactLevel int getRollbackImpactLevel() {
+ return mRollbackImpactLevel;
+ }
+
+ /**
+ * Set rollback impact level. Refer {@link
+ * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
+ * on impact level.
+ *
+ * @hide
+ */
+ public void setRollbackImpactLevel(
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
+ mRollbackImpactLevel = rollbackImpactLevel;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -124,16 +169,17 @@ public final class RollbackInfo implements Parcelable {
out.writeBoolean(mIsStaged);
out.writeTypedList(mCausePackages);
out.writeInt(mCommittedSessionId);
+ out.writeInt(mRollbackImpactLevel);
}
public static final @android.annotation.NonNull Parcelable.Creator<RollbackInfo> CREATOR =
new Parcelable.Creator<RollbackInfo>() {
- public RollbackInfo createFromParcel(Parcel in) {
- return new RollbackInfo(in);
- }
-
- public RollbackInfo[] newArray(int size) {
- return new RollbackInfo[size];
- }
- };
+ public RollbackInfo createFromParcel(Parcel in) {
+ return new RollbackInfo(in);
+ }
+
+ public RollbackInfo[] newArray(int size) {
+ return new RollbackInfo[size];
+ }
+ };
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8a4f678b52f2..35ae3c9e23c7 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -40,12 +40,15 @@ import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.NeverCompile;
@@ -103,8 +106,14 @@ public final class SQLiteDatabase extends SQLiteClosable {
// Stores reference to all databases opened in the current process.
// (The referent Object is not used at this time.)
// INVARIANT: Guarded by sActiveDatabases.
+ @GuardedBy("sActiveDatabases")
private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
+ // Tracks which database files are currently open. If a database file is opened more than
+ // once at any given moment, the associated databases are marked as "concurrent".
+ @GuardedBy("sActiveDatabases")
+ private static final OpenTracker sOpenTracker = new OpenTracker();
+
// Thread-local for database sessions that belong to this database.
// Each thread has its own database session.
// INVARIANT: Immutable.
@@ -510,6 +519,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
+ final String path;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
@@ -520,10 +530,12 @@ public final class SQLiteDatabase extends SQLiteClosable {
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
+ path = isInMemoryDatabase() ? null : getPath();
}
if (!finalized) {
synchronized (sActiveDatabases) {
+ sOpenTracker.close(path);
sActiveDatabases.remove(this);
}
@@ -1132,6 +1144,74 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
+ /**
+ * Track the number of times a database file has been opened. There is a primary connection
+ * associated with every open database, and these can contend with each other, leading to
+ * unexpected SQLiteDatabaseLockedException exceptions. The tracking here is only advisory:
+ * multiply-opened databases are logged but no other action is taken.
+ *
+ * This class is not thread-safe.
+ */
+ private static class OpenTracker {
+ // The list of currently-open databases. This maps the database file to the number of
+ // currently-active opens.
+ private final ArrayMap<String, Integer> mOpens = new ArrayMap<>();
+
+ // The maximum number of concurrently open database paths that will be stored. Once this
+ // many paths have been recorded, further paths are logged but not saved.
+ private static final int MAX_RECORDED_PATHS = 20;
+
+ // The list of databases that were ever concurrently opened.
+ private final ArraySet<String> mConcurrent = new ArraySet<>();
+
+ /** Return the canonical path. On error, just return the input path. */
+ private static String normalize(String path) {
+ try {
+ return new File(path).toPath().toRealPath().toString();
+ } catch (Exception e) {
+ // If there is an IO or security exception, just continue, using the input path.
+ return path;
+ }
+ }
+
+ /** Return true if the path is currently open in another SQLiteDatabase instance. */
+ void open(@Nullable String path) {
+ if (path == null) return;
+ path = normalize(path);
+
+ Integer count = mOpens.get(path);
+ if (count == null || count == 0) {
+ mOpens.put(path, 1);
+ return;
+ } else {
+ mOpens.put(path, count + 1);
+ if (mConcurrent.size() < MAX_RECORDED_PATHS) {
+ mConcurrent.add(path);
+ }
+ Log.w(TAG, "multiple primary connections on " + path);
+ return;
+ }
+ }
+
+ void close(@Nullable String path) {
+ if (path == null) return;
+ path = normalize(path);
+ Integer count = mOpens.get(path);
+ if (count == null || count <= 0) {
+ Log.e(TAG, "open database counting failure on " + path);
+ } else if (count == 1) {
+ // Implicitly set the count to zero, and make mOpens smaller.
+ mOpens.remove(path);
+ } else {
+ mOpens.put(path, count - 1);
+ }
+ }
+
+ ArraySet<String> getConcurrentDatabasePaths() {
+ return new ArraySet<>(mConcurrent);
+ }
+ }
+
private void open() {
try {
try {
@@ -1153,14 +1233,17 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
private void openInner() {
+ final String path;
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
+ path = isInMemoryDatabase() ? null : getPath();
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
+ sOpenTracker.open(path);
}
}
@@ -2345,6 +2428,17 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Return list of databases that have been concurrently opened.
+ * @hide
+ */
+ @VisibleForTesting
+ public static ArraySet<String> getConcurrentDatabasePaths() {
+ synchronized (sActiveDatabases) {
+ return sOpenTracker.getConcurrentDatabasePaths();
+ }
+ }
+
+ /**
* Returns true if the new version code is greater than the current database version.
*
* @param newVersion The new version code.
@@ -2766,6 +2860,19 @@ public final class SQLiteDatabase extends SQLiteClosable {
dumpDatabaseDirectory(printer, new File(dir), isSystem);
}
}
+
+ // Dump concurrently-opened database files, if any
+ final ArraySet<String> concurrent;
+ synchronized (sActiveDatabases) {
+ concurrent = sOpenTracker.getConcurrentDatabasePaths();
+ }
+ if (concurrent.size() > 0) {
+ printer.println("");
+ printer.println("Concurrently opened database files");
+ for (String f : concurrent) {
+ printer.println(" " + f);
+ }
+ }
}
private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 4fc5131617b2..505655775239 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -31,6 +31,10 @@ public class VintfObject {
private static final String LOG_TAG = "VintfObject";
+ static {
+ System.loadLibrary("vintf_jni");
+ }
+
/**
* Slurps all device information (both manifests and both matrices)
* and report them.
diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java
index f17039ba9bf4..e729063d6763 100644
--- a/core/java/android/os/VintfRuntimeInfo.java
+++ b/core/java/android/os/VintfRuntimeInfo.java
@@ -28,6 +28,10 @@ public class VintfRuntimeInfo {
private VintfRuntimeInfo() {}
+ static {
+ System.loadLibrary("vintf_jni");
+ }
+
/**
* @return /sys/fs/selinux/policyvers, via security_policyvers() native call
*
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 39b6aeb814f6..8c7050176506 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -86,3 +86,11 @@ flag {
description: "This flag is used to enabled the Wallet Role for all users on the device"
bug: "283989236"
}
+
+flag {
+ name: "signature_permission_allowlist_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enable signature permission allowlist"
+ bug: "308573169"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ab98c94cba73..d946430f18af 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3243,9 +3243,17 @@ public final class Settings {
private static final String NAME_EQ_PLACEHOLDER = "name=?";
+ // Cached values of queried settings.
+ // Key is the setting's name, value is the setting's value.
// Must synchronize on 'this' to access mValues and mValuesVersion.
private final ArrayMap<String, String> mValues = new ArrayMap<>();
+ // Cached values for queried prefixes.
+ // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's
+ // name to a setting's value. The name string doesn't include the prefix.
+ // Must synchronize on 'this' to access.
+ private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>();
+
private final Uri mUri;
@UnsupportedAppUsage
private final ContentProviderHolder mProviderHolder;
@@ -3592,15 +3600,13 @@ public final class Settings {
|| applicationInfo.isSignedWithPlatformKey();
}
- private ArrayMap<String, String> getStringsForPrefixStripPrefix(
- ContentResolver cr, String prefix, String[] names) {
+ private Map<String, String> getStringsForPrefixStripPrefix(
+ ContentResolver cr, String prefix, List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int substringLength = prefix.length();
-
int currentGeneration = -1;
boolean needsGenerationTracker = false;
-
synchronized (NameValueCache.this) {
final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
if (generationTracker != null) {
@@ -3614,40 +3620,24 @@ public final class Settings {
// generation tracker and request a new one
generationTracker.destroy();
mGenerationTrackers.remove(prefix);
- for (int i = mValues.size() - 1; i >= 0; i--) {
- String key = mValues.keyAt(i);
- if (key.startsWith(prefix)) {
- mValues.remove(key);
- }
- }
+ mPrefixToValues.remove(prefix);
needsGenerationTracker = true;
} else {
- boolean prefixCached = mValues.containsKey(prefix);
- if (prefixCached) {
- if (DEBUG) {
- Log.i(TAG, "Cache hit for prefix:" + prefix);
- }
- if (names.length > 0) {
+ final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix);
+ if (cachedSettings != null) {
+ if (!names.isEmpty()) {
for (String name : names) {
- // mValues can contain "null" values, need to use containsKey.
- if (mValues.containsKey(name)) {
+ // The cache can contain "null" values, need to use containsKey.
+ if (cachedSettings.containsKey(name)) {
keyValues.put(
- name.substring(substringLength),
- mValues.get(name));
+ name,
+ cachedSettings.get(name));
}
}
} else {
- for (int i = 0; i < mValues.size(); ++i) {
- String key = mValues.keyAt(i);
- // Explicitly exclude the prefix as it is only there to
- // signal that the prefix has been cached.
- if (key.startsWith(prefix) && !key.equals(prefix)) {
- String value = mValues.valueAt(i);
- keyValues.put(
- key.substring(substringLength),
- value);
- }
- }
+ keyValues.putAll(cachedSettings);
+ // Remove the hack added for the legacy behavior.
+ keyValues.remove("");
}
return keyValues;
}
@@ -3657,7 +3647,6 @@ public final class Settings {
needsGenerationTracker = true;
}
}
-
if (mCallListCommand == null) {
// No list command specified, return empty map
return keyValues;
@@ -3702,20 +3691,23 @@ public final class Settings {
}
// All flags for the namespace
- Map<String, String> flagsToValues =
+ HashMap<String, String> flagsToValues =
(HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
+ if (flagsToValues == null) {
+ return keyValues;
+ }
// Only the flags requested by the caller
- if (names.length > 0) {
+ if (!names.isEmpty()) {
for (String name : names) {
// flagsToValues can contain "null" values, need to use containsKey.
- if (flagsToValues.containsKey(name)) {
+ final String key = Config.createCompositeName(namespace, name);
+ if (flagsToValues.containsKey(key)) {
keyValues.put(
- name.substring(substringLength),
- flagsToValues.get(name));
+ name,
+ flagsToValues.get(key));
}
}
} else {
- keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
keyValues.put(
flag.getKey().substring(substringLength),
@@ -3751,10 +3743,18 @@ public final class Settings {
if (DEBUG) {
Log.i(TAG, "Updating cache for prefix:" + prefix);
}
- // cache the complete list of flags for the namespace
- mValues.putAll(flagsToValues);
- // Adding the prefix as a signal that the prefix is cached.
- mValues.put(prefix, null);
+ // Cache the complete list of flags for the namespace for bulk queries.
+ // In this cached list, the setting's name doesn't include the prefix.
+ ArrayMap<String, String> namesToValues =
+ new ArrayMap<>(flagsToValues.size() + 1);
+ for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+ namesToValues.put(
+ flag.getKey().substring(substringLength),
+ flag.getValue());
+ }
+ // Legacy behavior, we return <"", null> when queried with name = ""
+ namesToValues.put("", null);
+ mPrefixToValues.put(prefix, namesToValues);
}
}
return keyValues;
@@ -19945,16 +19945,9 @@ public final class Settings {
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
@NonNull String namespace, @NonNull List<String> names) {
- String[] compositeNames = new String[names.size()];
- for (int i = 0, size = names.size(); i < size; ++i) {
- compositeNames[i] = createCompositeName(namespace, names.get(i));
- }
-
String prefix = createPrefix(namespace);
- ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
- resolver, prefix, compositeNames);
- return keyValues;
+ return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names);
}
/**
@@ -20276,7 +20269,7 @@ public final class Settings {
}
}
- private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
+ static String createCompositeName(@NonNull String namespace, @NonNull String name) {
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(name);
var sb = new StringBuilder(namespace.length() + 1 + name.length());
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index eca848ae1ea3..1ea80f146b0b 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -152,7 +152,8 @@ public abstract class Layout {
/** @hide */
@IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
LineBreaker.JUSTIFICATION_MODE_NONE,
- LineBreaker.JUSTIFICATION_MODE_INTER_WORD
+ LineBreaker.JUSTIFICATION_MODE_INTER_WORD,
+ LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface JustificationMode {}
@@ -168,6 +169,13 @@ public abstract class Layout {
public static final int JUSTIFICATION_MODE_INTER_WORD =
LineBreaker.JUSTIFICATION_MODE_INTER_WORD;
+ /**
+ * Value for justification mode indicating the text is justified by stretching letter spacing.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
+ LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
+
/*
* Line spacing multiplier for default line spacing.
*/
@@ -809,7 +817,7 @@ public abstract class Layout {
getEllipsisStart(lineNum) + getEllipsisCount(lineNum),
isFallbackLineSpacingEnabled());
if (justify) {
- tl.justify(right - left - indentWidth);
+ tl.justify(mJustificationMode, right - left - indentWidth);
}
tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
@@ -1058,7 +1066,7 @@ public abstract class Layout {
getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
isFallbackLineSpacingEnabled());
if (isJustificationRequired(line)) {
- tl.justify(getJustifyWidth(line));
+ tl.justify(mJustificationMode, getJustifyWidth(line));
}
tl.metrics(null, rectF, false, null);
@@ -1794,7 +1802,7 @@ public abstract class Layout {
getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
isFallbackLineSpacingEnabled());
if (isJustificationRequired(line)) {
- tl.justify(getJustifyWidth(line));
+ tl.justify(mJustificationMode, getJustifyWidth(line));
}
final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
TextLine.recycle(tl);
@@ -1882,7 +1890,7 @@ public abstract class Layout {
getEllipsisStart(line), getEllipsisStart(line) + getEllipsisCount(line),
isFallbackLineSpacingEnabled());
if (isJustificationRequired(line)) {
- tl.justify(getJustifyWidth(line));
+ tl.justify(mJustificationMode, getJustifyWidth(line));
}
final float width = tl.metrics(null, null, mUseBoundsForWidth, null);
TextLine.recycle(tl);
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 2175b47e149e..224e5d8f7712 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -100,9 +100,25 @@ public class TextLine {
// Additional width of whitespace for justification. This value is per whitespace, thus
// the line width will increase by mAddedWidthForJustify x (number of stretchable whitespaces).
- private float mAddedWidthForJustify;
+ private float mAddedWordSpacingInPx;
+ private float mAddedLetterSpacingInPx;
private boolean mIsJustifying;
+ @VisibleForTesting
+ public float getAddedWordSpacingInPx() {
+ return mAddedWordSpacingInPx;
+ }
+
+ @VisibleForTesting
+ public float getAddedLetterSpacingInPx() {
+ return mAddedLetterSpacingInPx;
+ }
+
+ @VisibleForTesting
+ public boolean isJustifying() {
+ return mIsJustifying;
+ }
+
private final TextPaint mWorkPaint = new TextPaint();
private final TextPaint mActivePaint = new TextPaint();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -259,7 +275,7 @@ public class TextLine {
}
}
mTabs = tabStops;
- mAddedWidthForJustify = 0;
+ mAddedWordSpacingInPx = 0;
mIsJustifying = false;
mEllipsisStart = ellipsisStart != ellipsisEnd ? ellipsisStart : 0;
@@ -274,19 +290,42 @@ public class TextLine {
* Justify the line to the given width.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public void justify(float justifyWidth) {
+ public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
int end = mLen;
while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
end--;
}
- final int spaces = countStretchableSpaces(0, end);
- if (spaces == 0) {
- // There are no stretchable spaces, so we can't help the justification by adding any
- // width.
- return;
+ if (justificationMode == Layout.JUSTIFICATION_MODE_INTER_WORD) {
+ float width = Math.abs(measure(end, false, null, null, null));
+ final int spaces = countStretchableSpaces(0, end);
+ if (spaces == 0) {
+ // There are no stretchable spaces, so we can't help the justification by adding any
+ // width.
+ return;
+ }
+ mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
+ mAddedLetterSpacingInPx = 0;
+ } else { // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+ LineInfo lineInfo = new LineInfo();
+ float width = Math.abs(measure(end, false, null, null, lineInfo));
+
+ int lettersCount = lineInfo.getClusterCount();
+ if (lettersCount < 2) {
+ return;
+ }
+ mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1);
+ if (mAddedLetterSpacingInPx > 0.03) {
+ // If the letter spacing is more than 0.03em, the ligatures are automatically
+ // disabled, so re-calculate everything without ligatures.
+ final String oldFontFeatures = mPaint.getFontFeatureSettings();
+ mPaint.setFontFeatureSettings(oldFontFeatures + ", \"liga\" off, \"cliga\" off");
+ width = Math.abs(measure(end, false, null, null, lineInfo));
+ lettersCount = lineInfo.getClusterCount();
+ mAddedLetterSpacingInPx = (justifyWidth - width) / (lettersCount - 1);
+ mPaint.setFontFeatureSettings(oldFontFeatures);
+ }
+ mAddedWordSpacingInPx = 0;
}
- final float width = Math.abs(measure(end, false, null, null, null));
- mAddedWidthForJustify = (justifyWidth - width) / spaces;
mIsJustifying = true;
}
@@ -529,6 +568,9 @@ public class TextLine {
throw new IndexOutOfBoundsException(
"offset(" + offset + ") should be less than line limit(" + mLen + ")");
}
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(0);
+ }
final int target = trailing ? offset - 1 : offset;
if (target < 0) {
return 0;
@@ -1076,7 +1118,8 @@ public class TextLine {
TextPaint wp = mWorkPaint;
wp.set(mPaint);
if (mIsJustifying) {
- wp.setWordSpacing(mAddedWidthForJustify);
+ wp.setWordSpacing(mAddedWordSpacingInPx);
+ wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize()); // Convert to Em
}
int spanStart = runStart;
@@ -1277,7 +1320,8 @@ public class TextLine {
@Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
int runFlag) {
if (mIsJustifying) {
- wp.setWordSpacing(mAddedWidthForJustify);
+ wp.setWordSpacing(mAddedWordSpacingInPx);
+ wp.setLetterSpacing(mAddedLetterSpacingInPx / wp.getTextSize()); // Convert to Em
}
// Get metrics first (even for empty strings or "0" width runs)
if (drawBounds != null && fmi == null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0bda9181380..3c36227eda0a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,7 +26,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1012,10 +1011,8 @@ public final class ViewRootImpl implements ViewParent,
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the frame rate boosting period.
+ // Used to check if it is in the touch boosting period.
private boolean mIsFrameRateBoosting = false;
- // Used to check if it is in touch boosting period.
- private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -6424,12 +6421,11 @@ public final class ViewRootImpl implements ViewParent,
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
- mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -7452,7 +7448,7 @@ public final class ViewRootImpl implements ViewParent,
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsTouchBoosting = true;
+ mIsFrameRateBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -12204,16 +12200,8 @@ public final class ViewRootImpl implements ViewParent,
return;
}
- int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
-
- // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
- // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
- // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
- // (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
- frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- }
+ int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
+ ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c8fd246a255b..656cc3e2af71 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -188,8 +188,6 @@ cc_library_shared_for_libandroid_runtime {
"android_os_SharedMemory.cpp",
"android_os_storage_StorageManager.cpp",
"android_os_UEventObserver.cpp",
- "android_os_VintfObject.cpp",
- "android_os_VintfRuntimeInfo.cpp",
"android_os_incremental_IncrementalManager.cpp",
"android_net_LocalSocketImpl.cpp",
"android_service_DataLoaderService.cpp",
@@ -280,6 +278,7 @@ cc_library_shared_for_libandroid_runtime {
"libdmabufinfo",
"libgif",
"libgui_window_info_static",
+ "libkernelconfigs",
"libseccomp_policy",
"libgrallocusage",
"libscrypt_static",
@@ -350,7 +349,6 @@ cc_library_shared_for_libandroid_runtime {
"libnativeloader_lazy",
"libmemunreachable",
"libhidlbase",
- "libvintf",
"libnativedisplay",
"libnativewindow",
"libdl",
@@ -458,8 +456,25 @@ cc_library_shared_for_libandroid_runtime {
// (e.g. gDefaultServiceManager)
"libbinder",
"libhidlbase", // libhwbinder is in here
- "libvintf",
],
},
},
}
+
+cc_library_shared {
+ name: "libvintf_jni",
+
+ cpp_std: "gnu++20",
+
+ srcs: [
+ "android_os_VintfObject.cpp",
+ "android_os_VintfRuntimeInfo.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libnativehelper",
+ "libvintf",
+ ],
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 7a16318f3276..aa63f4fa03d4 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,8 +152,6 @@ extern int register_android_os_MessageQueue(JNIEnv* env);
extern int register_android_os_Parcel(JNIEnv* env);
extern int register_android_os_PerformanceHintManager(JNIEnv* env);
extern int register_android_os_SELinux(JNIEnv* env);
-extern int register_android_os_VintfObject(JNIEnv *env);
-extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
extern int register_android_os_storage_StorageManager(JNIEnv* env);
extern int register_android_os_SystemProperties(JNIEnv *env);
extern int register_android_os_SystemClock(JNIEnv* env);
@@ -1545,8 +1543,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_NativeHandle),
REG_JNI(register_android_os_ServiceManager),
REG_JNI(register_android_os_storage_StorageManager),
- REG_JNI(register_android_os_VintfObject),
- REG_JNI(register_android_os_VintfRuntimeInfo),
REG_JNI(register_android_service_DataLoaderService),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index de1ce4e29198..1504a00f972c 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -52,7 +52,7 @@
#include <memunreachable/memunreachable.h>
#include <android-base/strings.h>
#include "android_os_Debug.h"
-#include <vintf/VintfObject.h>
+#include <vintf/KernelConfigs.h>
namespace android
{
@@ -1004,10 +1004,9 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz)
} cfg_state = CONFIG_UNKNOWN;
if (cfg_state == CONFIG_UNKNOWN) {
- auto runtime_info = vintf::VintfObject::GetInstance()->getRuntimeInfo(
- vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
- CHECK(runtime_info != nullptr) << "Kernel configs cannot be fetched. b/151092221";
- const std::map<std::string, std::string>& configs = runtime_info->kernelConfigs();
+ std::map<std::string, std::string> configs;
+ const status_t result = android::kernelconfigs::LoadKernelConfigs(&configs);
+ CHECK(result == OK) << "Kernel configs could not be fetched. b/151092221";
std::map<std::string, std::string>::const_iterator it = configs.find("CONFIG_VMAP_STACK");
cfg_state = (it != configs.end() && it->second == "y") ? CONFIG_SET : CONFIG_UNSET;
}
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 477bd096b11a..734b5f497e2e 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -39,7 +39,6 @@
#include <hwbinder/ProcessState.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <vintf/parse_string.h>
#include <utils/misc.h>
#include "core_jni_helpers.h"
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index a5b2f65eafc7..ce4a33735c6d 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -17,16 +17,14 @@
#define LOG_TAG "VintfObject"
//#define LOG_NDEBUG 0
#include <android-base/logging.h>
-
-#include <vector>
-#include <string>
-
-#include <nativehelper/JNIHelp.h>
#include <vintf/VintfObject.h>
#include <vintf/parse_string.h>
#include <vintf/parse_xml.h>
-#include "core_jni_helpers.h"
+#include <vector>
+#include <string>
+
+#include "jni_wrappers.h"
static jclass gString;
static jclass gHashMapClazz;
@@ -94,7 +92,7 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass)
return toJavaStringArray(env, cStrings);
}
-static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv*, jclass) {
std::string error;
// Use temporary VintfObject, not the shared instance, to release memory
// after check.
@@ -204,4 +202,23 @@ int register_android_os_VintfObject(JNIEnv* env)
NELEM(gVintfObjectMethods));
}
-};
+extern int register_android_os_VintfRuntimeInfo(JNIEnv* env);
+
+} // namespace android
+
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ JNIEnv* env = NULL;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+ return JNI_ERR;
+ }
+
+ if (android::register_android_os_VintfObject(env) < 0) {
+ return JNI_ERR;
+ }
+
+ if (android::register_android_os_VintfRuntimeInfo(env) < 0) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
index b0271b9e92af..7c2f58829446 100644
--- a/core/jni/android_os_VintfRuntimeInfo.cpp
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -17,23 +17,22 @@
#define LOG_TAG "VintfRuntimeInfo"
//#define LOG_NDEBUG 0
-#include <nativehelper/JNIHelp.h>
#include <vintf/VintfObject.h>
#include <vintf/parse_string.h>
#include <vintf/parse_xml.h>
-#include "core_jni_helpers.h"
+#include "jni_wrappers.h"
namespace android {
using vintf::RuntimeInfo;
using vintf::VintfObject;
-#define MAP_STRING_METHOD(javaMethod, cppString, flags) \
- static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) { \
- std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \
- if (info == nullptr) return nullptr; \
- return env->NewStringUTF((cppString).c_str()); \
+#define MAP_STRING_METHOD(javaMethod, cppString, flags) \
+ static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass) { \
+ std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \
+ if (info == nullptr) return nullptr; \
+ return env->NewStringUTF((cppString).c_str()); \
}
MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO);
@@ -49,9 +48,7 @@ MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()),
MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()),
RuntimeInfo::FetchFlag::AVB);
-
-static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
-{
+static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv*, jclass) {
std::shared_ptr<const RuntimeInfo> info =
VintfObject::GetRuntimeInfo(RuntimeInfo::FetchFlag::POLICYVERS);
if (info == nullptr) return 0;
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index 210dc895d674..769fa723c96e 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -22,6 +22,8 @@
#include <nativehelper/scoped_utf_chars.h>
#include <android_runtime/AndroidRuntime.h>
+#include "jni_wrappers.h"
+
// Host targets (layoutlib) do not differentiate between regular and critical native methods,
// and they need all the JNI methods to have JNIEnv* and jclass/jobject as their first two arguments.
// The following macro allows to have those arguments when compiling for host while omitting them when
@@ -36,60 +38,6 @@
namespace android {
-// Defines some helpful functions.
-
-static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
- jclass clazz = env->FindClass(class_name);
- LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
- return clazz;
-}
-
-static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
- const char* field_signature) {
- jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
- field_signature);
- return res;
-}
-
-static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
- const char* method_signature) {
- jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
- method_signature);
- return res;
-}
-
-static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
- const char* field_signature) {
- jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
- field_signature);
- return res;
-}
-
-static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
- const char* method_signature) {
- jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
- method_name, method_signature);
- return res;
-}
-
-template <typename T>
-static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
- jobject res = env->NewGlobalRef(in);
- LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
- return static_cast<T>(res);
-}
-
-static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
- const JNINativeMethod* gMethods, int numMethods) {
- int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
- LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
- return res;
-}
-
/**
* Returns the result of invoking java.lang.ref.Reference.get() on a Reference object.
*/
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
new file mode 100644
index 000000000000..3b29e305e410
--- /dev/null
+++ b/core/jni/jni_wrappers.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// JNI wrappers for better logging
+
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
+ jclass clazz = env->FindClass(class_name);
+ LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
+ return clazz;
+}
+
+static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+ const char* field_signature) {
+ jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
+ field_signature);
+ return res;
+}
+
+static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+ const char* method_signature) {
+ jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
+ method_signature);
+ return res;
+}
+
+static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+ const char* field_signature) {
+ jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
+ field_signature);
+ return res;
+}
+
+static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+ const char* method_signature) {
+ jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
+ method_name, method_signature);
+ return res;
+}
+
+template <typename T>
+static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
+ jobject res = env->NewGlobalRef(in);
+ LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
+ return static_cast<T>(res);
+}
+
+static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods, int numMethods) {
+ int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+ return res;
+}
+
+} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b199420537d5..52cf67981bb3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3569,6 +3569,13 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to set policy related to <a
+ href="https://www.threadgroup.org">Thread</a> network.
+ @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to set policy related to windows.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
@@ -4961,7 +4968,7 @@
<p>Protection level: signature
-->
<permission android:name="android.permission.BIND_NFC_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|module" />
<!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService}
to ensure that only the system can bind to it.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5be29a6d68b8..9e1400641084 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -57,6 +57,7 @@
<item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
@@ -102,6 +103,7 @@
<string translatable="false" name="status_bar_call_strength">call_strength</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
<string translatable="false" name="status_bar_screen_record">screen_record</string>
+ <string translatable="false" name="status_bar_oem_satellite">satellite</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
@@ -5334,20 +5336,19 @@
and a second time clipped to the fill level to indicate charge -->
<bool name="config_batterymeterDualTone">false</bool>
- <!-- The default refresh rate for a given device. Change this value to set a higher default
- refresh rate. If the hardware composer on the device supports display modes with a higher
- refresh rate than the default value specified here, the framework may use those higher
- refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
- setFrameRate().
- If a non-zero value is set for config_defaultPeakRefreshRate, then
- config_defaultRefreshRate may be set to 0, in which case the value set for
- config_defaultPeakRefreshRate will act as the default frame rate. -->
- <integer name="config_defaultRefreshRate">60</integer>
-
- <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
- the framework from using higher refresh rates, even if display modes with higher refresh
- rates are available from hardware composer. Only has an effect if the value is
- non-zero. -->
+ <!-- The default refresh rate for a given device. This value is used to set the
+ global refresh rate vote, and when set to zero it has no effect on the vote.
+ If this value is non-zero but the hardware composer on the device supports
+ display modes with higher refresh rates, the framework may use those higher
+ refresh rate modes if an app chooses one by setting preferredDisplayModeId
+ or calling setFrameRate().-->
+ <integer name="config_defaultRefreshRate">0</integer>
+
+ <!-- The default peak refresh rate for a given device. This value is used to set the
+ global peak refresh rate vote, and when set to zero it has no effect on the vote.
+ Change this value to non-zero if you want to prevent the framework from using higher
+ refresh rates, even if display modes with higher refresh rates are available from
+ hardware composer. -->
<integer name="config_defaultPeakRefreshRate">0</integer>
<!-- External display peak refresh rate for the given device. Change this value if you want to
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d12ef2b95f06..ef12d8f4ac0f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3198,6 +3198,7 @@
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
<java-symbol type="string" name="status_bar_screen_record" />
+ <java-symbol type="string" name="status_bar_oem_satellite" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index bbac69f35e81..4f9b2697ee89 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -728,10 +729,10 @@ public final class RadioManagerTest {
@Test
public void equals_withFmBandConfigsOfDifferentAfSupportValues() {
- RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
- new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
- FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
- !AF_SUPPORTED, EA_SUPPORTED));
+ RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
+ createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
+ .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
+ RadioManager.FmBandConfig fmBandConfigCompared = builder.build();
assertWithMessage("FM Band Config of different af support value")
.that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
@@ -1300,6 +1301,18 @@ public final class RadioManagerTest {
}
@Test
+ public void addAnnouncementListener_withListenerAddedBeforeAndCloseException_throws()
+ throws Exception {
+ createRadioManager();
+ Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
+ mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener);
+ doThrow(new RemoteException()).when(mCloseHandleMock).close();
+
+ assertThrows(RuntimeException.class,
+ () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener));
+ }
+
+ @Test
public void addAnnouncementListener_whenServiceDied_throwException() throws Exception {
createRadioManager();
String exceptionMessage = "service is dead";
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 4841711f712d..4cda26de2906 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -125,6 +125,16 @@ public final class TunerAdapterTest {
}
@Test
+ public void close_forTunerAdapterCalledTwice() throws Exception {
+ mRadioTuner.close();
+ verify(mTunerMock).close();
+
+ mRadioTuner.close();
+
+ verify(mTunerMock).close();
+ }
+
+ @Test
public void setConfiguration_forTunerAdapter() throws Exception {
int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG);
@@ -134,6 +144,12 @@ public final class TunerAdapterTest {
}
@Test
+ public void setConfiguration_withNull_fails() throws Exception {
+ assertWithMessage("Status for setting configuration with null")
+ .that(mRadioTuner.setConfiguration(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE);
+ }
+
+ @Test
public void setConfiguration_withInvalidParameters_fails() throws Exception {
doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any());
@@ -840,6 +856,15 @@ public final class TunerAdapterTest {
}
@Test
+ public void onTuneFailed_withDeadService() throws Exception {
+ mTunerCallback.onTuneFailed(RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+ RadioManager.STATUS_DEAD_OBJECT, FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SERVER_DIED);
+ }
+
+ @Test
public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
mTunerCallback.onProgramListChanged();
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index e118c98dd4da..3ee565f8e025 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -403,4 +403,41 @@ public class SQLiteDatabaseTest {
}
assertFalse(allowed);
}
+
+ /** Return true if the path is in the list of strings. */
+ private boolean isConcurrent(String path) throws Exception {
+ path = new File(path).toPath().toRealPath().toString();
+ return SQLiteDatabase.getConcurrentDatabasePaths().contains(path);
+ }
+
+ @Test
+ public void testDuplicateDatabases() throws Exception {
+ // The two database paths in this test are assumed not to have been opened earlier in this
+ // process.
+
+ // A database path that will be opened twice.
+ final String dbName = "never-used-db.db";
+ final File dbFile = mContext.getDatabasePath(dbName);
+ final String dbPath = dbFile.getPath();
+
+ // A database path that will be opened only once.
+ final String okName = "never-used-ok.db";
+ final File okFile = mContext.getDatabasePath(okName);
+ final String okPath = okFile.getPath();
+
+ SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+ assertFalse(isConcurrent(dbPath));
+ SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+ assertTrue(isConcurrent(dbPath));
+ db1.close();
+ assertTrue(isConcurrent(dbPath));
+ db2.close();
+ assertTrue(isConcurrent(dbPath));
+
+ SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+ db3.close();
+ db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
+ assertFalse(isConcurrent(okPath));
+ db3.close();
+ }
}
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
new file mode 100644
index 000000000000..a52561557523
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineJustificationTest {
+
+ @Rule
+ @JvmField
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val PAINT = TextPaint().apply {
+ textSize = 10f // make 1em = 10px
+ }
+
+ private fun makeTextLine(cs: CharSequence, paint: TextPaint) = TextLine.obtain().apply {
+ set(paint, cs, 0, cs.length, Layout.DIR_LEFT_TO_RIGHT, Layout.DIRS_ALL_LEFT_TO_RIGHT, false,
+ null, 0, 0, false)
+ }
+
+ private fun getClusterCount(cs: CharSequence, paint: TextPaint) = TextLine.LineInfo().apply {
+ makeTextLine(cs, paint).also {
+ it.metrics(null, null, false, this)
+ TextLine.recycle(it)
+ }
+ }.clusterCount
+
+ fun justifyTest_WithoutJustify() {
+ val line = "Hello, World."
+ val tl = makeTextLine(line, PAINT)
+
+ // Without calling justify method, justifying should be false and all added spaces should
+ // be zeros.
+ assertThat(tl.isJustifying).isFalse()
+ assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+ assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+ }
+
+ @Test
+ fun justifyTest_IntrCharacter_Latin() {
+ val line = "Hello, World."
+ val clusterCount = getClusterCount(line, PAINT)
+ val originalWidth = Layout.getDesiredWidth(line, PAINT)
+ val extraWidth = 100f
+
+ val tl = makeTextLine(line, PAINT)
+ tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth)
+
+ assertThat(tl.isJustifying).isTrue()
+ assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+ assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1))
+
+ TextLine.recycle(tl)
+ }
+
+ @Test
+ fun justifyTest_IntrCharacter_Japanese() {
+ val line = "\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002"
+ val clusterCount = getClusterCount(line, PAINT)
+ val originalWidth = Layout.getDesiredWidth(line, PAINT)
+ val extraWidth = 100f
+
+ val tl = makeTextLine(line, PAINT)
+ tl.justify(Layout.JUSTIFICATION_MODE_INTER_CHARACTER, originalWidth + extraWidth)
+
+ assertThat(tl.isJustifying).isTrue()
+ assertThat(tl.addedWordSpacingInPx).isEqualTo(0)
+ assertThat(tl.addedLetterSpacingInPx).isEqualTo(extraWidth / (clusterCount - 1))
+
+ TextLine.recycle(tl)
+ }
+
+ @Test
+ fun justifyTest_IntrWord_Latin() {
+ val line = "Hello, World."
+ val originalWidth = Layout.getDesiredWidth(line, PAINT)
+ val extraWidth = 100f
+
+ val tl = makeTextLine(line, PAINT)
+ tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth)
+
+ assertThat(tl.isJustifying).isTrue()
+ // This text contains only one whitespace, so word spacing should be same to the extraWidth.
+ assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth)
+ assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+
+ TextLine.recycle(tl)
+ }
+
+ @Test
+ fun justifyTest_IntrWord_Japanese() {
+ val line = "\u672C\u65E5\u306F\u6674\u0020\u5929\u306A\u308A\u3002"
+ val originalWidth = Layout.getDesiredWidth(line, PAINT)
+ val extraWidth = 100f
+
+ val tl = makeTextLine(line, PAINT)
+ tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, originalWidth + extraWidth)
+
+ assertThat(tl.isJustifying).isTrue()
+ // This text contains only one whitespace, so word spacing should be same to the extraWidth.
+ assertThat(tl.addedWordSpacingInPx).isEqualTo(extraWidth)
+ assertThat(tl.addedLetterSpacingInPx).isEqualTo(0)
+
+ TextLine.recycle(tl)
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index a31992c8cfa1..8ae5669de55f 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -53,7 +53,7 @@ public class TextLineTest {
final float originalWidth = tl.metrics(null, null, false, null);
final float expandedWidth = 2 * originalWidth;
- tl.justify(expandedWidth);
+ tl.justify(Layout.JUSTIFICATION_MODE_INTER_WORD, expandedWidth);
final float newWidth = tl.metrics(null, null, false, null);
TextLine.recycle(tl);
return Math.abs(newWidth - expandedWidth) < 0.5;
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cfbda84f24e1..cf3eb12498ca 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,7 +19,6 @@ package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -576,13 +575,8 @@ public class ViewRootImplTest {
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 607b4bdf71ca..294b8ae0f6f5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -23,40 +23,48 @@ applications that come with the platform
<!-- Needed for Build.getSerial(), which is used to send a unique number for serial, per HUIG. -->
<privapp-permissions package="android.car.usb.handler">
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.angle">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.apps.tag">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.backupconfirm">
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.CRYPT_KEEPER"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.externalstorage">
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.imsserviceentitlement">
<permission name="android.permission.MODIFY_PHONE_STATE" />
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.launcher3">
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.location.fused">
<permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.managedprovisioning">
@@ -79,12 +87,14 @@ applications that come with the platform
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.mms.service">
<permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
<permission name="android.permission.BIND_CARRIER_SERVICES"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.mtp">
@@ -94,16 +104,19 @@ applications that come with the platform
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.musicfx">
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.networkrecommendation">
<permission name="android.permission.SCORE_NETWORKS"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.packageinstaller">
@@ -114,6 +127,7 @@ applications that come with the platform
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.phone">
@@ -170,6 +184,7 @@ applications that come with the platform
<permission name="android.permission.LOG_COMPAT_CHANGE"/>
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.UWB_PRIVILEGED"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.providers.calendar">
@@ -180,6 +195,7 @@ applications that come with the platform
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.LOG_COMPAT_CHANGE" />
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.providers.contacts">
@@ -193,6 +209,7 @@ applications that come with the platform
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<permission name="android.permission.LOG_COMPAT_CHANGE" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.providers.downloads">
@@ -205,6 +222,7 @@ applications that come with the platform
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.providers.telephony">
@@ -214,6 +232,7 @@ applications that come with the platform
<!-- Permissions required for reading and logging compat changes -->
<permission name="android.permission.LOG_COMPAT_CHANGE" />
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.server.telecom">
@@ -229,11 +248,13 @@ applications that come with the platform
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.sharedstoragebackup">
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.shell">
@@ -547,16 +568,19 @@ applications that come with the platform
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
<permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
<permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.soundpicker">
<permission name="android.permission.INTERACT_ACROSS_USERS" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.tv">
@@ -568,15 +592,18 @@ applications that come with the platform
<permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
<permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
<permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.vpndialogs">
<permission name="android.permission.CONTROL_VPN"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.wallpaper.livepicker">
<permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
<permission name="android.permission.BIND_WALLPAPER"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.wallpaper">
@@ -584,25 +611,30 @@ applications that come with the platform
<permission name="android.permission.BIND_WALLPAPER"/>
<permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
<permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.dynsystem">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
<permission name="android.permission.READ_OEM_UNLOCK_STATE"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.settings">
<permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
<permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.bips">
<permission name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
<privapp-permissions package="com.android.calllogbackup">
<permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
+ <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
</privapp-permissions>
</permissions>
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 0e3fb163ef75..97071260402c 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,6 +17,7 @@
package android.graphics.text;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
@@ -163,7 +164,8 @@ public class LineBreaker {
/** @hide */
@IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
JUSTIFICATION_MODE_NONE,
- JUSTIFICATION_MODE_INTER_WORD
+ JUSTIFICATION_MODE_INTER_WORD,
+ JUSTIFICATION_MODE_INTER_CHARACTER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface JustificationMode {}
@@ -179,6 +181,12 @@ public class LineBreaker {
public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
/**
+ * Value for justification mode indicating the text is justified by stretching letter spacing.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
+
+ /**
* Helper class for creating a {@link LineBreaker}.
*/
public static final class Builder {
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
index 30d5edb59c85..160f922dd928 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -199,6 +199,10 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
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);
@@ -211,12 +215,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
private void finishAnimation() {
if (mEnteringTarget != null) {
- mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
- mEnteringTarget.leash.release();
+ if (mEnteringTarget.leash != null && mEnteringTarget.leash.isValid()) {
+ mTransaction.setCornerRadius(mEnteringTarget.leash, 0);
+ mEnteringTarget.leash.release();
+ }
mEnteringTarget = null;
}
if (mClosingTarget != null) {
- mClosingTarget.leash.release();
+ if (mClosingTarget.leash != null) {
+ mClosingTarget.leash.release();
+ }
mClosingTarget = null;
}
if (mBackground != null) {
@@ -260,7 +268,9 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
}
private void onGestureCommitted() {
- if (mEnteringTarget == null || mClosingTarget == null) {
+ if (mEnteringTarget == null || mClosingTarget == null || mClosingTarget.leash == null
+ || mEnteringTarget.leash == null || !mEnteringTarget.leash.isValid()
+ || !mClosingTarget.leash.isValid()) {
finishAnimation();
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index ac2a1c867462..adc78391f033 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -208,7 +208,9 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float top = mapRange(progress, mClosingStartRect.top, targetTop);
float width = mapRange(progress, mClosingStartRect.width(), targetWidth);
float height = mapRange(progress, mClosingStartRect.height(), targetHeight);
- mTransaction.setLayer(mClosingTarget.leash, 0);
+ if (mClosingTarget.leash != null && mClosingTarget.leash.isValid()) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ }
mClosingCurrentRect.set(left, top, left + width, top + height);
applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
@@ -226,7 +228,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
/** Transform the target window to match the target rect. */
private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
- if (leash == null) {
+ if (leash == null || !leash.isValid()) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d4a92894a397..a5f7880cfd41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -137,7 +137,7 @@ import java.util.function.IntConsumer;
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
public class BubbleController implements ConfigurationChangeListener,
- RemoteCallable<BubbleController> {
+ RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -706,6 +706,7 @@ public class BubbleController implements ConfigurationChangeListener,
return mBubbleIconFactory;
}
+ @Override
public Bubbles.SysuiProxy getSysuiProxy() {
return mSysuiProxy;
}
@@ -732,8 +733,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, this, mBubbleData, mSurfaceSynchronizer,
- mFloatingContentCoordinator,
- mMainExecutor);
+ mFloatingContentCoordinator, this, mMainExecutor);
mStackView.onOrientationChanged();
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c25d41275f2b..a619401301aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -206,6 +206,7 @@ public class BubbleStackView extends FrameLayout
};
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
+ private final Bubbles.SysuiProxy.Provider mSysuiProxyProvider;
private StackViewState mStackViewState = new StackViewState();
private final ValueAnimator mDismissBubbleAnimator;
@@ -875,12 +876,14 @@ public class BubbleStackView extends FrameLayout
public BubbleStackView(Context context, BubbleController bubbleController,
BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
+ Bubbles.SysuiProxy.Provider sysuiProxyProvider,
ShellExecutor mainExecutor) {
super(context);
mMainExecutor = mainExecutor;
mBubbleController = bubbleController;
mBubbleData = data;
+ mSysuiProxyProvider = sysuiProxyProvider;
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
@@ -2090,7 +2093,7 @@ public class BubbleStackView extends FrameLayout
hideCurrentInputMethod();
- mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
+ mSysuiProxyProvider.getSysuiProxy().onStackExpandChanged(shouldExpand);
if (wasExpanded) {
stopMonitoringSwipeUpGesture();
@@ -3034,7 +3037,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
mManageMenuScrim.setVisibility(INVISIBLE);
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(false /* show */);
return;
}
if (show) {
@@ -3048,7 +3051,7 @@ public class BubbleStackView extends FrameLayout
}
};
- mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
+ mSysuiProxyProvider.getSysuiProxy().onManageMenuExpandChanged(show);
mManageMenuScrim.animate()
.setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
.alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 759246eb285d..28af0ca6ac6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -321,6 +321,13 @@ public interface Bubbles {
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
+
+ /** Provider interface for {@link SysuiProxy}. */
+ interface Provider {
+ /** Returns {@link SysuiProxy}. */
+ SysuiProxy getSysuiProxy();
+ }
+
void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 9ce46d69815b..e9cd73b0df5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -95,8 +95,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
- + "entering PIP from an Activity Embedding window");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for entering PIP from"
+ + " an Activity Embedding window #%d", info.getDebugId());
// Split into two transitions (wct)
TransitionInfo.Change pipChange = null;
final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
@@ -146,6 +146,8 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent"
+ + " with a remote transition and PIP #%d", info.getDebugId());
boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
info, startTransaction, finishTransaction, finishCallback);
// Consume the transition on remote handler if the leftover handler already handle this
@@ -192,8 +194,9 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
}
return false;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
- + " animation because remote-animation likely doesn't support it");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Splitting PIP into a separate"
+ + " animation because remote-animation likely doesn't support it #%d",
+ info.getDebugId());
// Split the transition into 2 parts: the pip part and the rest.
mInFlightSubAnimations = 2;
// make a new startTransaction because pip's startEnterAnimation "consumes" it so
@@ -218,6 +221,9 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for unfolding #%d",
+ info.getDebugId());
+
final Transitions.TransitionFinishCallback finishCB = (wct) -> {
mInFlightSubAnimations--;
if (mInFlightSubAnimations > 0) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 643e0266d7df..4ea71490798c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -30,9 +30,11 @@ import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.splitscreen.StageCoordinator;
@@ -77,6 +79,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition for Recents during"
+ + " Desktop #%d", info.getDebugId());
+
if (mInfo == null) {
mInfo = info;
mFinishT = finishTransaction;
@@ -109,6 +114,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " Keyguard #%d", info.getDebugId());
+
if (mInfo == null) {
mInfo = info;
mFinishT = finishTransaction;
@@ -122,6 +130,9 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ + " split screen #%d", info.getDebugId());
+
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// Pip auto-entering info might be appended to recent transition like pressing
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index f58332198696..4878df806f82 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -125,7 +125,7 @@ class BubbleViewInfoTest : ShellTestCase() {
mock<BubbleProperties>())
bubbleStackView = BubbleStackView(context, bubbleController, bubbleData,
- surfaceSynchronizer, FloatingContentCoordinator(), mainExecutor)
+ surfaceSynchronizer, FloatingContentCoordinator(), bubbleController, mainExecutor)
bubbleBarLayerView = BubbleBarLayerView(context, bubbleController)
}
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index 4d020c567972..5f5ffe97e953 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -21,6 +21,7 @@
#include <include/gpu/GrDirectContext.h>
#include <include/gpu/GrBackendSurface.h>
#include <include/gpu/MutableTextureState.h>
+#include <include/gpu/vk/VulkanMutableTextureState.h>
#include "renderthread/RenderThread.h"
#include "utils/Color.h"
#include "utils/PaintUtils.h"
@@ -142,8 +143,9 @@ void AutoBackendTextureRelease::releaseQueueOwnership(GrDirectContext* context)
LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
if (mBackendTexture.isValid()) {
// Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
- skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
- VK_QUEUE_FAMILY_FOREIGN_EXT);
+ skgpu::MutableTextureState newState = skgpu::MutableTextureStates::MakeVulkan(
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_QUEUE_FAMILY_FOREIGN_EXT);
// The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
// releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp
index acf893e9544c..79acb6cc35e5 100644
--- a/libs/hwui/jni/PathMeasure.cpp
+++ b/libs/hwui/jni/PathMeasure.cpp
@@ -17,7 +17,11 @@
#include "GraphicsJNI.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
#include "SkPathMeasure.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
/* We declare an explicit pair, so that we don't have to rely on the java
client to be sure not to edit the path while we have an active measure
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index 5f1279ecceea..8c7c8716b2dc 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -36,15 +36,15 @@ flag {
}
flag {
- name: "gnss_configuration_from_resource"
+ name: "replace_future_elapsed_realtime_jni"
namespace: "location"
- description: "Flag for GNSS configuration from resource"
- bug: "317734846"
+ description: "Flag for replacing future elapsedRealtime in JNI"
+ bug: "314328533"
}
flag {
- name: "replace_future_elapsed_realtime_jni"
+ name: "gnss_configuration_from_resource"
namespace: "location"
- description: "Flag for replacing future elapsedRealtime in JNI"
- bug: "314328533"
+ description: "Flag for GNSS configuration from resource"
+ bug: "317734846"
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2eec9b3d4a09..8dfa6be0fd11 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -754,15 +754,16 @@ interface IAudioService {
void unregisterLoudnessCodecUpdatesDispatcher(in ILoudnessCodecUpdatesDispatcher dispatcher);
- oneway void startLoudnessCodecUpdates(int piid, in List<LoudnessCodecInfo> codecInfoSet);
+ oneway void startLoudnessCodecUpdates(int sessionId);
- oneway void stopLoudnessCodecUpdates(int piid);
+ oneway void stopLoudnessCodecUpdates(int sessionId);
- oneway void addLoudnessCodecInfo(int piid, int mediaCodecHash, in LoudnessCodecInfo codecInfo);
+ oneway void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ in LoudnessCodecInfo codecInfo);
- oneway void removeLoudnessCodecInfo(int piid, in LoudnessCodecInfo codecInfo);
+ oneway void removeLoudnessCodecInfo(int sessionId, in LoudnessCodecInfo codecInfo);
- PersistableBundle getLoudnessParams(int piid, in LoudnessCodecInfo codecInfo);
+ PersistableBundle getLoudnessParams(in LoudnessCodecInfo codecInfo);
@EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
diff --git a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
index 16eaaea38b7f..202242755a97 100644
--- a/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
+++ b/media/java/android/media/ILoudnessCodecUpdatesDispatcher.aidl
@@ -16,6 +16,7 @@
package android.media;
+import android.media.AudioAttributes;
import android.os.PersistableBundle;
/**
@@ -26,6 +27,6 @@ import android.os.PersistableBundle;
*/
oneway interface ILoudnessCodecUpdatesDispatcher {
- void dispatchLoudnessCodecParameterChange(int piid, in PersistableBundle params);
+ void dispatchLoudnessCodecParameterChange(int sessionId, in PersistableBundle params);
} \ No newline at end of file
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecController.java
index aadd78328d68..b3e5c52b27b3 100644
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -16,13 +16,13 @@
package android.media;
-import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D;
import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.media.permission.SafeCloseable;
import android.os.Bundle;
import android.util.Log;
@@ -32,7 +32,6 @@ import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -41,16 +40,21 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Class for getting recommended loudness parameter updates for audio decoders, according to the
- * encoded format and current audio routing. Those updates can be automatically applied to the
- * {@link MediaCodec} instance(s), or be provided to the user. The codec loudness management
- * parameter updates are defined by the CTA-2075 standard.
- * <p>A new object should be instantiated for each {@link AudioTrack} with the help
- * of {@link #create()} or {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
+ * Class for getting recommended loudness parameter updates for audio decoders as they are used
+ * to play back media content according to the encoded format and current audio routing. These
+ * audio decoder updates leverage loudness metadata present in compressed audio streams. They
+ * ensure the loudness and dynamic range of the content is optimized to the physical
+ * characteristics of the audio output device (e.g. phone microspeakers vs headphones vs TV
+ * speakers).Those updates can be automatically applied to the {@link MediaCodec} instance(s), or
+ * be provided to the user. The codec loudness management parameter updates are computed in
+ * accordance to the CTA-2075 standard.
+ * <p>A new object should be instantiated for each audio session
+ * (see {@link AudioManager#generateAudioSessionId()}) using creator methods {@link #create(int)} or
+ * {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-public class LoudnessCodecConfigurator {
- private static final String TAG = "LoudnessCodecConfigurator";
+public class LoudnessCodecController implements SafeCloseable {
+ private static final String TAG = "LoudnessCodecController";
/**
* Listener used for receiving asynchronous loudness metadata updates.
@@ -67,6 +71,7 @@ public class LoudnessCodecConfigurator {
* directly on the mediaCodec. The listener can modify
* these values with their own edits which will be
* returned for the mediaCodec configuration
+ *
* @return a Bundle which contains the original computed codecValues
* aggregated with user edits. The platform will configure the associated
* MediaCodecs with the returned Bundle params.
@@ -74,159 +79,116 @@ public class LoudnessCodecConfigurator {
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
@NonNull
default Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
- @NonNull Bundle codecValues) {
+ @NonNull Bundle codecValues) {
return codecValues;
}
}
- @NonNull private final LoudnessCodecDispatcher mLcDispatcher;
-
- private final Object mConfiguratorLock = new Object();
-
- @GuardedBy("mConfiguratorLock")
- private AudioTrack mAudioTrack;
+ @NonNull
+ private final LoudnessCodecDispatcher mLcDispatcher;
- @GuardedBy("mConfiguratorLock")
- private final Executor mExecutor;
+ private final Object mControllerLock = new Object();
- @GuardedBy("mConfiguratorLock")
- private final OnLoudnessCodecUpdateListener mListener;
+ private final int mSessionId;
- @GuardedBy("mConfiguratorLock")
+ @GuardedBy("mControllerLock")
private final HashMap<LoudnessCodecInfo, Set<MediaCodec>> mMediaCodecs = new HashMap<>();
/**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
+ * Creates a new instance of {@link LoudnessCodecController}
*
* <p>This method should be used when the client does not need to alter the
* codec loudness parameters before they are applied to the audio decoders.
- * Otherwise, use {@link #create(Executor, OnLoudnessCodecUpdateListener)}.
+ * Otherwise, use {@link #create(int, Executor, OnLoudnessCodecUpdateListener)}.
*
- * @return the {@link LoudnessCodecConfigurator} instance
+ * @param sessionId the session ID of the track that will receive data
+ * from the added {@link MediaCodec}'s
+ *
+ * @return the {@link LoudnessCodecController} instance
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public static @NonNull LoudnessCodecConfigurator create() {
- return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
- Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
+ public static @NonNull LoudnessCodecController create(int sessionId) {
+ final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(
+ AudioManager.getService());
+ final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+ sessionId);
+ dispatcher.addLoudnessCodecListener(controller, Executors.newSingleThreadExecutor(),
+ new OnLoudnessCodecUpdateListener() {});
+ dispatcher.startLoudnessCodecUpdates(sessionId);
+ return controller;
}
/**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
+ * Creates a new instance of {@link LoudnessCodecController}
*
* <p>This method should be used when the client wants to alter the codec
* loudness parameters before they are applied to the audio decoders.
- * Otherwise, use {@link #create()}.
+ * Otherwise, use {@link #create( int)}.
*
- * @param executor {@link Executor} to handle the callbacks
- * @param listener used for receiving updates
+ * @param sessionId the session ID of the track that will receive data
+ * from the added {@link MediaCodec}'s
+ * @param executor {@link Executor} to handle the callbacks
+ * @param listener used for receiving updates
*
- * @return the {@link LoudnessCodecConfigurator} instance
+ * @return the {@link LoudnessCodecController} instance
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public static @NonNull LoudnessCodecConfigurator create(
+ public static @NonNull LoudnessCodecController create(
+ int sessionId,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnLoudnessCodecUpdateListener listener) {
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
- return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(AudioManager.getService()),
- executor, listener);
+ final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(
+ AudioManager.getService());
+ final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+ sessionId);
+ dispatcher.addLoudnessCodecListener(controller, executor, listener);
+ dispatcher.startLoudnessCodecUpdates(sessionId);
+ return controller;
}
/**
- * Creates a new instance of {@link LoudnessCodecConfigurator}
+ * Creates a new instance of {@link LoudnessCodecController}
*
* <p>This method should be used only in testing
*
- * @param service interface for communicating with AudioService
+ * @param sessionId the session ID of the track that will receive data
+ * from the added {@link MediaCodec}'s
* @param executor {@link Executor} to handle the callbacks
* @param listener used for receiving updates
+ * @param service interface for communicating with AudioService
*
- * @return the {@link LoudnessCodecConfigurator} instance
- *
+ * @return the {@link LoudnessCodecController} instance
* @hide
*/
- public static @NonNull LoudnessCodecConfigurator createForTesting(
- @NonNull IAudioService service,
+ public static @NonNull LoudnessCodecController createForTesting(
+ int sessionId,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnLoudnessCodecUpdateListener listener) {
+ @NonNull OnLoudnessCodecUpdateListener listener,
+ @NonNull IAudioService service) {
Objects.requireNonNull(service, "IAudioService cannot be null");
Objects.requireNonNull(executor, "Executor cannot be null");
Objects.requireNonNull(listener, "OnLoudnessCodecUpdateListener cannot be null");
- return new LoudnessCodecConfigurator(new LoudnessCodecDispatcher(service),
- executor, listener);
+ final LoudnessCodecDispatcher dispatcher = new LoudnessCodecDispatcher(service);
+ final LoudnessCodecController controller = new LoudnessCodecController(dispatcher,
+ sessionId);
+ dispatcher.addLoudnessCodecListener(controller, executor, listener);
+ dispatcher.startLoudnessCodecUpdates(sessionId);
+ return controller;
}
/** @hide */
- private LoudnessCodecConfigurator(@NonNull LoudnessCodecDispatcher lcDispatcher,
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnLoudnessCodecUpdateListener listener) {
+ private LoudnessCodecController(@NonNull LoudnessCodecDispatcher lcDispatcher, int sessionId) {
mLcDispatcher = Objects.requireNonNull(lcDispatcher, "Dispatcher cannot be null");
- mExecutor = Objects.requireNonNull(executor, "Executor cannot be null");
- mListener = Objects.requireNonNull(listener,
- "OnLoudnessCodecUpdateListener cannot be null");
- }
-
- /**
- * Sets the {@link AudioTrack} and starts receiving asynchronous updates for
- * the registered {@link MediaCodec}s (see {@link #addMediaCodec(MediaCodec)})
- *
- * <p>The AudioTrack should be the one that receives audio data from the
- * added audio decoders and is used to determine the device routing on which
- * the audio streaming will take place. This will directly influence the
- * loudness parameters.
- * <p>After calling this method the framework will compute the initial set of
- * parameters which will be applied to the registered codecs/returned to the
- * listener for modification.
- *
- * @param audioTrack the track that will receive audio data from the provided
- * audio decoders. In case this is {@code null} this
- * method will have the effect of clearing the existing set
- * {@link AudioTrack} and will stop receiving asynchronous
- * loudness updates
- */
- @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setAudioTrack(@Nullable AudioTrack audioTrack) {
- List<LoudnessCodecInfo> codecInfos;
- int piid = PLAYER_PIID_INVALID;
- int oldPiid = PLAYER_PIID_INVALID;
- synchronized (mConfiguratorLock) {
- if (mAudioTrack != null && mAudioTrack == audioTrack) {
- Log.v(TAG, "Loudness configurator already started for piid: "
- + mAudioTrack.getPlayerIId());
- return;
- }
-
- codecInfos = getLoudnessCodecInfoList_l();
- if (mAudioTrack != null) {
- oldPiid = mAudioTrack.getPlayerIId();
- mLcDispatcher.removeLoudnessCodecListener(this);
- }
- if (audioTrack != null) {
- piid = audioTrack.getPlayerIId();
- mLcDispatcher.addLoudnessCodecListener(this, mExecutor, mListener);
- }
-
- mAudioTrack = audioTrack;
- }
-
- if (oldPiid != PLAYER_PIID_INVALID) {
- Log.v(TAG, "Loudness configurator stopping updates for piid: " + oldPiid);
- mLcDispatcher.stopLoudnessCodecUpdates(oldPiid);
- }
- if (piid != PLAYER_PIID_INVALID) {
- Log.v(TAG, "Loudness configurator starting updates for piid: " + piid);
- mLcDispatcher.startLoudnessCodecUpdates(piid, codecInfos);
- }
+ mSessionId = sessionId;
}
/**
- * Adds a new {@link MediaCodec} that will stream data to an {@link AudioTrack}
- * which the client sets
- * (see {@link LoudnessCodecConfigurator#setAudioTrack(AudioTrack)}).
- *
- * <p>This method can be called while asynchronous updates are live.
+ * Adds a new {@link MediaCodec} that will stream data to a player
+ * which uses {@link #mSessionId}.
*
* <p>No new element will be added if the passed {@code mediaCodec} was
* previously added.
@@ -234,23 +196,22 @@ public class LoudnessCodecConfigurator {
* @param mediaCodec the codec to start receiving asynchronous loudness
* updates. The codec has to be in a configured or started
* state in order to add it for loudness updates.
+ * @return {@code false} if the {@code mediaCodec} was not configured or does
+ * not contain loudness metadata, {@code true} otherwise.
* @throws IllegalArgumentException if the same {@code mediaCodec} was already
* added before.
- * @return {@code false} if the {@code mediaCodec} was not configured or does
- * not contain loudness metadata, {@code true} otherwise.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
final MediaCodec mc = Objects.requireNonNull(mediaCodec,
"MediaCodec for addMediaCodec cannot be null");
- int piid = PLAYER_PIID_INVALID;
final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
if (mcInfo == null) {
Log.v(TAG, "Could not extract codec loudness information");
return false;
}
- synchronized (mConfiguratorLock) {
+ synchronized (mControllerLock) {
final AtomicBoolean containsCodec = new AtomicBoolean(false);
Set<MediaCodec> newSet = mMediaCodecs.computeIfPresent(mcInfo, (info, codecSet) -> {
containsCodec.set(!codecSet.add(mc));
@@ -263,16 +224,12 @@ public class LoudnessCodecConfigurator {
}
if (containsCodec.get()) {
throw new IllegalArgumentException(
- "Loudness configurator already added " + mediaCodec);
- }
- if (mAudioTrack != null) {
- piid = mAudioTrack.getPlayerIId();
+ "Loudness controller already added " + mediaCodec);
}
}
- if (piid != PLAYER_PIID_INVALID) {
- mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
- }
+ mLcDispatcher.addLoudnessCodecInfo(mSessionId, mediaCodec.hashCode(),
+ mcInfo);
return true;
}
@@ -291,7 +248,6 @@ public class LoudnessCodecConfigurator {
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
public void removeMediaCodec(@NonNull MediaCodec mediaCodec) {
- int piid = PLAYER_PIID_INVALID;
LoudnessCodecInfo mcInfo;
AtomicBoolean removedMc = new AtomicBoolean(false);
AtomicBoolean removeInfo = new AtomicBoolean(false);
@@ -302,10 +258,7 @@ public class LoudnessCodecConfigurator {
if (mcInfo == null) {
throw new IllegalArgumentException("Could not extract codec loudness information");
}
- synchronized (mConfiguratorLock) {
- if (mAudioTrack != null) {
- piid = mAudioTrack.getPlayerIId();
- }
+ synchronized (mControllerLock) {
mMediaCodecs.computeIfPresent(mcInfo, (format, mcs) -> {
removedMc.set(mcs.remove(mediaCodec));
if (mcs.isEmpty()) {
@@ -317,68 +270,81 @@ public class LoudnessCodecConfigurator {
});
if (!removedMc.get()) {
throw new IllegalArgumentException(
- "Loudness configurator does not contain " + mediaCodec);
+ "Loudness controller does not contain " + mediaCodec);
}
}
- if (piid != PLAYER_PIID_INVALID && removeInfo.get()) {
- mLcDispatcher.removeLoudnessCodecInfo(piid, mcInfo);
+ if (removeInfo.get()) {
+ mLcDispatcher.removeLoudnessCodecInfo(mSessionId, mcInfo);
}
}
/**
- * Gets synchronous loudness updates when no listener is required. The provided
- * {@link MediaCodec} streams audio data to the passed {@link AudioTrack}.
+ * Returns the loudness parameters of the registered audio decoders
+ *
+ * <p>Those parameters may have been automatically applied if the
+ * {@code LoudnessCodecController} was created with {@link #create(int)}, or they are the
+ * parameters that have been sent to the {@link OnLoudnessCodecUpdateListener} if using a
+ * codec update listener.
*
- * @param audioTrack track that receives audio data from the passed
- * {@link MediaCodec}
- * @param mediaCodec codec that decodes loudness annotated data for the passed
- * {@link AudioTrack}
+ * @param mediaCodec codec that decodes loudness annotated data. Has to be added
+ * with {@link #addMediaCodec(MediaCodec)} before calling this
+ * method
+ * @throws IllegalArgumentException if the passed {@link MediaCodec} was not
+ * added before calling this method
*
- * @return the {@link Bundle} containing the current loudness parameters. Caller is
- * responsible to update the {@link MediaCodec}
+ * @return the {@link Bundle} containing the current loudness parameters.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
@NonNull
- public Bundle getLoudnessCodecParams(@NonNull AudioTrack audioTrack,
- @NonNull MediaCodec mediaCodec) {
- Objects.requireNonNull(audioTrack, "Passed audio track cannot be null");
+ public Bundle getLoudnessCodecParams(@NonNull MediaCodec mediaCodec) {
+ Objects.requireNonNull(mediaCodec, "MediaCodec cannot be null");
LoudnessCodecInfo codecInfo = getCodecInfo(mediaCodec);
if (codecInfo == null) {
- return new Bundle();
+ throw new IllegalArgumentException("MediaCodec does not have valid codec information");
}
- return mLcDispatcher.getLoudnessCodecParams(audioTrack.getPlayerIId(), codecInfo);
+ synchronized (mControllerLock) {
+ final Set<MediaCodec> codecs = mMediaCodecs.get(codecInfo);
+ if (codecs == null || !codecs.contains(mediaCodec)) {
+ throw new IllegalArgumentException(
+ "MediaCodec was not added for loudness annotation");
+ }
+ }
+
+ return mLcDispatcher.getLoudnessCodecParams(codecInfo);
}
- /** @hide */
- /*package*/ int getAssignedTrackPiid() {
- int piid = PLAYER_PIID_INVALID;
+ /**
+ * Stops any loudness updates and frees up the resources.
+ */
+ @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
+ public void release() {
+ close();
+ }
- synchronized (mConfiguratorLock) {
- if (mAudioTrack == null) {
- return piid;
- }
- piid = mAudioTrack.getPlayerIId();
+ /** @hide */
+ @Override
+ public void close() {
+ synchronized (mControllerLock) {
+ mMediaCodecs.clear();
}
+ mLcDispatcher.stopLoudnessCodecUpdates(mSessionId);
+ }
- return piid;
+ /** @hide */
+ /*package*/ int getSessionId() {
+ return mSessionId;
}
/** @hide */
/*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
- synchronized (mConfiguratorLock) {
+ synchronized (mControllerLock) {
return mMediaCodecs;
}
}
- @GuardedBy("mConfiguratorLock")
- private List<LoudnessCodecInfo> getLoudnessCodecInfoList_l() {
- return mMediaCodecs.values().stream().flatMap(listMc -> listMc.stream().map(
- LoudnessCodecConfigurator::getCodecInfo)).toList();
- }
-
@Nullable
private static LoudnessCodecInfo getCodecInfo(@NonNull MediaCodec mediaCodec) {
LoudnessCodecInfo lci = new LoudnessCodecInfo();
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index b546a81b0498..46be54be55ec 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -21,7 +21,7 @@ import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
import android.annotation.CallbackExecutor;
-import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
+import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -32,7 +32,6 @@ import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -59,7 +58,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>
+ private final HashMap<OnLoudnessCodecUpdateListener, LoudnessCodecController>
mConfiguratorListener = new HashMap<>();
public static synchronized LoudnessCodecUpdatesDispatcherStub getInstance() {
@@ -72,16 +71,16 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
private LoudnessCodecUpdatesDispatcherStub() {}
@Override
- public void dispatchLoudnessCodecParameterChange(int piid, PersistableBundle params) {
+ public void dispatchLoudnessCodecParameterChange(int sessionId, PersistableBundle params) {
if (DEBUG) {
- Log.d(TAG, "dispatchLoudnessCodecParameterChange for piid " + piid
+ Log.d(TAG, "dispatchLoudnessCodecParameterChange for sessionId " + sessionId
+ " persistable bundle: " + params);
}
mLoudnessListenerMgr.callListeners(listener -> {
synchronized (mLock) {
mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
// send the appropriate bundle for the user to update
- if (lcConfig.getAssignedTrackPiid() == piid) {
+ if (lcConfig.getSessionId() == sessionId) {
final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
lcConfig.getRegisteredMediaCodecs();
for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
@@ -111,7 +110,12 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
bundle));
if (!bundle.isDefinitelyEmpty()) {
- mediaCodec.setParameters(bundle);
+ try {
+ mediaCodec.setParameters(bundle);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Cannot set loudness bundle on media codec "
+ + mediaCodec);
+ }
}
if (canBreak) {
break;
@@ -145,7 +149,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
}
void addLoudnessCodecListener(@NonNull CallbackUtil.DispatcherStub dispatcher,
- @NonNull LoudnessCodecConfigurator configurator,
+ @NonNull LoudnessCodecController configurator,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnLoudnessCodecUpdateListener listener) {
Objects.requireNonNull(configurator);
@@ -160,15 +164,15 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
}
}
- void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+ void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) {
Objects.requireNonNull(configurator);
OnLoudnessCodecUpdateListener listenerToRemove = null;
synchronized (mLock) {
- Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator>> iterator =
+ Iterator<Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController>> iterator =
mConfiguratorListener.entrySet().iterator();
while (iterator.hasNext()) {
- Entry<OnLoudnessCodecUpdateListener, LoudnessCodecConfigurator> e =
+ Entry<OnLoudnessCodecUpdateListener, LoudnessCodecController> e =
iterator.next();
if (e.getValue() == configurator) {
final OnLoudnessCodecUpdateListener listener = e.getKey();
@@ -208,7 +212,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
}
/** @hide */
- public void addLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator,
+ public void addLoudnessCodecListener(@NonNull LoudnessCodecController configurator,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnLoudnessCodecUpdateListener listener) {
LoudnessCodecUpdatesDispatcherStub.getInstance().addLoudnessCodecListener(this,
@@ -216,52 +220,52 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
}
/** @hide */
- public void removeLoudnessCodecListener(@NonNull LoudnessCodecConfigurator configurator) {
+ public void removeLoudnessCodecListener(@NonNull LoudnessCodecController configurator) {
LoudnessCodecUpdatesDispatcherStub.getInstance().removeLoudnessCodecListener(configurator);
}
/** @hide */
- public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+ public void startLoudnessCodecUpdates(int sessionId) {
try {
- mAudioService.startLoudnessCodecUpdates(piid, codecInfoList);
+ mAudioService.startLoudnessCodecUpdates(sessionId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/** @hide */
- public void stopLoudnessCodecUpdates(int piid) {
+ public void stopLoudnessCodecUpdates(int sessionId) {
try {
- mAudioService.stopLoudnessCodecUpdates(piid);
+ mAudioService.stopLoudnessCodecUpdates(sessionId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/** @hide */
- public void addLoudnessCodecInfo(int piid, int mediaCodecHash,
+ public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
@NonNull LoudnessCodecInfo mcInfo) {
try {
- mAudioService.addLoudnessCodecInfo(piid, mediaCodecHash, mcInfo);
+ mAudioService.addLoudnessCodecInfo(sessionId, mediaCodecHash, mcInfo);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/** @hide */
- public void removeLoudnessCodecInfo(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+ public void removeLoudnessCodecInfo(int sessionId, @NonNull LoudnessCodecInfo mcInfo) {
try {
- mAudioService.removeLoudnessCodecInfo(piid, mcInfo);
+ mAudioService.removeLoudnessCodecInfo(sessionId, mcInfo);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/** @hide */
- public Bundle getLoudnessCodecParams(int piid, @NonNull LoudnessCodecInfo mcInfo) {
+ public Bundle getLoudnessCodecParams(@NonNull LoudnessCodecInfo mcInfo) {
Bundle loudnessParams = null;
try {
- loudnessParams = new Bundle(mAudioService.getLoudnessParams(piid, mcInfo));
+ loudnessParams = new Bundle(mAudioService.getLoudnessParams(mcInfo));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 587e35b4b1fc..5e40eee26886 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -17,6 +17,7 @@
package android.media;
import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -121,6 +122,10 @@ import java.util.stream.Collectors;
* <tr><td>{@link #KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT}</td>
* <td>Integer</td><td><b>decoder-only</b>, optional, if content is MPEG-H audio,
* specifies the preferred reference channel layout of the stream.</td></tr>
+ * <tr><td>{@link #KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE}</td><td>Integer</td><td>optional, used with
+ * large audio frame support, specifies max size of output buffer in bytes.</td></tr>
+ * <tr><td>{@link #KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}</td><td>Integer</td><td>optional,
+ * used with large audio frame support, specifies threshold output size in bytes.</td></tr>
* </table>
*
* Subtitle formats have the following keys:
@@ -459,6 +464,50 @@ public final class MediaFormat {
public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
/**
+ * A key describing the maximum output buffer size in bytes when using
+ * large buffer mode containing multiple access units.
+ *
+ * When not-set - codec functions with one access-unit per frame.
+ * When set less than the size of two access-units - will make codec
+ * operate in single access-unit per output frame.
+ * When set to a value too big - The component or the framework will
+ * override this value to a reasonable max size not exceeding typical
+ * 10 seconds of data (device dependent) when set to a value larger than
+ * that. The value final value used will be returned in the output format.
+ *
+ * The associated value is an integer
+ *
+ * @see FEATURE_MultipleFrames
+ */
+ @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+ public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+
+ /**
+ * A key describing the threshold output size in bytes when using large buffer
+ * mode containing multiple access units.
+ *
+ * This is an optional parameter.
+ *
+ * If not set - the component can set this to a reasonable value.
+ * If set larger than max size, the components will
+ * clip this setting to maximum buffer batching output size.
+ *
+ * The component will return a partial output buffer if the output buffer reaches or
+ * surpass this limit.
+ *
+ * Threshold size should be always less or equal to KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE.
+ * The final setting of this value as determined by the component will be returned
+ * in the output format
+ *
+ * The associated value is an integer
+ *
+ * @see FEATURE_MultipleFrames
+ */
+ @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+ public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE =
+ "buffer-batch-threshold-output-size";
+
+ /**
* A key describing the pixel aspect ratio width.
* The associated value is an integer
*/
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 8e9c07996f83..a0f8ae5defeb 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1154,6 +1154,7 @@ public class MediaPlayer extends PlayerBase
setDataSource(afd);
return true;
} catch (NullPointerException | SecurityException | IOException ex) {
+ Log.w(TAG, "Error setting data source via ContentResolver", ex);
return false;
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 17f25255fd4b..687feef6c58a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -3109,9 +3109,8 @@ public final class MediaRouter2 {
mStub, mDiscoveryPreference);
}
- if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
- unregisterRouterStubLocked();
- }
+ unregisterRouterStubIfNeededLocked();
+
} catch (RemoteException ex) {
Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex);
}
@@ -3319,13 +3318,12 @@ public final class MediaRouter2 {
obtainMessage(MediaRouter2::notifyStop, MediaRouter2.this, controller));
}
- if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) {
- try {
- unregisterRouterStubLocked();
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
+ try {
+ unregisterRouterStubIfNeededLocked();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
}
+
}
}
@@ -3339,8 +3337,10 @@ public final class MediaRouter2 {
}
@GuardedBy("mLock")
- private void unregisterRouterStubLocked() throws RemoteException {
- if (mStub != null) {
+ private void unregisterRouterStubIfNeededLocked() throws RemoteException {
+ if (mStub != null
+ && mRouteCallbackRecords.isEmpty()
+ && mNonSystemRoutingControllers.isEmpty()) {
mMediaRouterService.unregisterRouter2(mStub);
mStub = null;
}
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 74b6fc13ade4..71147f4becb5 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -46,6 +46,11 @@ import java.util.StringTokenizer;
* <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
* <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
* audio effects.
+ *
+ * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the
+ * platform with regards to spatialization, a different name for audio channel virtualization,
+ * and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to
+ * characterize how you want your content to be played when spatialization is supported.
*/
public class Virtualizer extends AudioEffect {
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 0f8a00a3c7d9..897827750d4e 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -44,6 +44,7 @@ oneway interface ITvInputClient {
void onTrackSelected(int type, in String trackId, int seq);
void onVideoAvailable(int seq);
void onVideoUnavailable(int reason, int seq);
+ void onVideoFreezeUpdated(boolean isFrozen, int seq);
void onContentAllowed(int seq);
void onContentBlocked(in String rating, int seq);
void onLayoutSurface(int left, int top, int right, int bottom, int seq);
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index a52e9a572dca..8e2702a50662 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -41,6 +41,7 @@ oneway interface ITvInputSessionCallback {
void onTrackSelected(int type, in String trackId);
void onVideoAvailable();
void onVideoUnavailable(int reason);
+ void onVideoFreezeUpdated(boolean isFrozen);
void onContentAllowed();
void onContentBlocked(in String rating);
void onLayoutSurface(int left, int top, int right, int bottom);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 51b2542688a6..caddd8ad674f 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -740,6 +740,15 @@ public final class TvInputManager {
}
/**
+ * This is called when the video freeze state has been updated.
+ * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ * @param session A {@link TvInputManager.Session} associated with this callback.
+ * @param isFrozen Whether the video is frozen
+ */
+ public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+ }
+
+ /**
* This is called when the current program content turns out to be allowed to watch since
* its content rating is not blocked by parental controls.
*
@@ -1030,6 +1039,19 @@ public final class TvInputManager {
});
}
+ void postVideoFreezeUpdated(boolean isFrozen) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onVideoFreezeUpdated(mSession, isFrozen);
+ if (mSession.mIAppNotificationEnabled
+ && mSession.getInteractiveAppSession() != null) {
+ mSession.getInteractiveAppSession().notifyVideoFreezeUpdated(isFrozen);
+ }
+ }
+ });
+ }
+
void postContentAllowed() {
mHandler.post(new Runnable() {
@Override
@@ -1546,6 +1568,18 @@ public final class TvInputManager {
}
@Override
+ public void onVideoFreezeUpdated(boolean isFrozen, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postVideoFreezeUpdated(isFrozen);
+ }
+ }
+
+ @Override
public void onContentAllowed(int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 76d8e50f94be..6301a27bac4d 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -763,6 +763,34 @@ public abstract class TvInputService extends Service {
}
/**
+ * Informs the application that the video freeze state has been updated.
+ *
+ * When {@code true}, the video is frozen on the last frame but audio playback remains
+ * active.
+ *
+ * @param isFrozen Whether or not the video is frozen
+ * @hide
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onVideoFreezeUpdated(isFrozen);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in notifyVideoFreezeUpdated", e);
+ }
+ }
+ });
+ }
+
+ /**
* Sends an updated list of all audio presentations available from a Next Generation Audio
* service. This is used by the framework to maintain the audio presentation information for
* a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 4316d053a275..0f58b29247bb 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -88,6 +88,7 @@ interface ITvInteractiveAppManager {
void notifyTracksChanged(in IBinder sessionToken, in List<TvTrackInfo> tracks, int userId);
void notifyVideoAvailable(in IBinder sessionToken, int userId);
void notifyVideoUnavailable(in IBinder sessionToken, int reason, int userId);
+ void notifyVideoFreezeUpdated(in IBinder sessionToken, boolean isFrozen, int userId);
void notifyContentAllowed(in IBinder sessionToken, int userId);
void notifyContentBlocked(in IBinder sessionToken, in String rating, int userId);
void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index ba7cf13a7a1d..06808c9ff915 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -67,6 +67,7 @@ oneway interface ITvInteractiveAppSession {
void notifyTracksChanged(in List<TvTrackInfo> tracks);
void notifyVideoAvailable();
void notifyVideoUnavailable(int reason);
+ void notifyVideoFreezeUpdated(boolean isFrozen);
void notifyContentAllowed();
void notifyContentBlocked(in String rating);
void notifySignalStrength(int strength);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 518b08a93f95..77730aa46d0a 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -103,6 +103,7 @@ public class ITvInteractiveAppSessionWrapper
private static final int DO_SEND_TIME_SHIFT_MODE = 46;
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
+ private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -364,6 +365,10 @@ public class ITvInteractiveAppSessionWrapper
args.recycle();
break;
}
+ case DO_NOTIFY_VIDEO_FREEZE_UPDATED: {
+ mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -552,6 +557,12 @@ public class ITvInteractiveAppSessionWrapper
}
@Override
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_VIDEO_FREEZE_UPDATED,
+ isFrozen));
+ }
+
+ @Override
public void notifyContentAllowed() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_CONTENT_ALLOWED));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index bf4379f470d8..8a340f6862bb 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1731,6 +1731,22 @@ public final class TvInteractiveAppManager {
}
/**
+ * Notifies Interactive app session when the video freeze state is updated
+ * @param isFrozen Whether or not the video is frozen
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.notifyVideoFreezeUpdated(mToken, isFrozen, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies Interactive APP session when content is allowed.
*/
public void notifyContentAllowed() {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 79364034ac2a..5247a0ebe6e0 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -882,6 +882,15 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Called when video becomes frozen or unfrozen. Audio playback will continue while
+ * video will be frozen to the last frame if {@code true}.
+ * @param isFrozen Whether or not the video is frozen.
+ * @hide
+ */
+ public void onVideoFreezeUpdated(boolean isFrozen) {
+ }
+
+ /**
* Called when content is allowed.
*/
public void onContentAllowed() {
@@ -1770,6 +1779,13 @@ public abstract class TvInteractiveAppService extends Service {
onVideoUnavailable(reason);
}
+ void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated (isFrozen=" + isFrozen + ")");
+ }
+ onVideoFreezeUpdated(isFrozen);
+ }
+
void notifyContentAllowed() {
if (DEBUG) {
Log.d(TAG, "notifyContentAllowed");
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 40a12e4db4cc..5bb61c261ae2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -719,6 +719,22 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
+ * Alerts the TV Interactive app that the video freeze state has been updated.
+ * If {@code true}, the video is frozen on the last frame while audio playback continues.
+ *
+ * @param isFrozen Whether the video is frozen.
+ * @hide
+ */
+ public void notifyVideoFreezeUpdated(boolean isFrozen) {
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoFreezeUpdated");
+ }
+ if (mSession != null) {
+ mSession.notifyVideoFreezeUpdated(isFrozen);
+ }
+ }
+
+ /**
* Sends signing result to related TV interactive app.
*
* <p>This is used when the corresponding server of the broadcast-independent interactive
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
index 74e5612c0486..4f6ede508f7a 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -24,19 +24,16 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioTrack;
+import android.media.AudioManager;
import android.media.IAudioService;
-import android.media.LoudnessCodecConfigurator;
-import android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener;
+import android.media.LoudnessCodecController;
+import android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
@@ -54,21 +51,19 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.List;
import java.util.concurrent.Executors;
/**
- * Unit tests for {@link LoudnessCodecConfigurator} checking the internal interactions with a mocked
+ * Unit tests for {@link LoudnessCodecController} checking the internal interactions with a mocked
* {@link IAudioService} without any real IPC interactions.
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class LoudnessCodecConfiguratorTest {
+public class LoudnessCodecControllerTest {
private static final String TAG = "LoudnessCodecConfiguratorTest";
private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
@@ -84,76 +79,41 @@ public class LoudnessCodecConfiguratorTest {
@Mock
private IAudioService mAudioService;
- private LoudnessCodecConfigurator mLcc;
+ private LoudnessCodecController mLcc;
+
+ private int mSessionId;
@Before
public void setUp() {
- mLcc = LoudnessCodecConfigurator.createForTesting(mAudioService,
- Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {});
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setAudioTrack_callsAudioServiceStart() throws Exception {
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
-
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- anyList());
- } finally {
- mediaCodec.release();
+ final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final AudioManager audioManager = (AudioManager) context.getSystemService(
+ AudioManager.class);
+ mSessionId = 0;
+ if (audioManager != null) {
+ mSessionId = audioManager.generateAudioSessionId();
}
+ mLcc = LoudnessCodecController.createForTesting(mSessionId,
+ Executors.newSingleThreadExecutor(), new OnLoudnessCodecUpdateListener() {
+ }, mAudioService);
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
- when(mAudioService.getLoudnessParams(anyInt(), any())).thenReturn(new PersistableBundle());
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.getLoudnessCodecParams(track, mediaCodec);
-
- verify(mAudioService).getLoudnessParams(eq(track.getPlayerIId()), any());
- } finally {
- mediaCodec.release();
- }
+ public void createLcc_callsAudioServiceStart() throws Exception {
+ verify(mAudioService).startLoudnessCodecUpdates(eq(mSessionId));
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setAudioTrack_addsAudioServicePiidCodecs() throws Exception {
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
-
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
- } finally {
- mediaCodec.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setAudioTrackTwice_ignoresSecondCall() throws Exception {
- final AudioTrack track = createAudioTrack();
+ public void getLoudnessCodecParams_callsAudioServiceGetLoudness() throws Exception {
+ when(mAudioService.getLoudnessParams(any())).thenReturn(new PersistableBundle());
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- mLcc.setAudioTrack(track);
+ mLcc.getLoudnessCodecParams(mediaCodec);
- verify(mAudioService, times(1)).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- anyList());
+ verify(mAudioService).getLoudnessParams(any());
} finally {
mediaCodec.release();
}
@@ -161,16 +121,14 @@ public class LoudnessCodecConfiguratorTest {
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setTrackNull_stopCodecUpdates() throws Exception {
- final AudioTrack track = createAudioTrack();
+ public void release_stopCodecUpdates() throws Exception {
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
+ mLcc.release(); // stops updats
- mLcc.setAudioTrack(null); // stops updates
- verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
+ verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
} finally {
mediaCodec.release();
}
@@ -204,40 +162,14 @@ public class LoudnessCodecConfiguratorTest {
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
- final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
- final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
-
- try {
- mLcc.addMediaCodec(mediaCodec1);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()),
- argument.capture());
- assertEquals(argument.getValue().size(), 1);
-
- mLcc.addMediaCodec(mediaCodec2);
- mLcc.setAudioTrack(null);
- verify(mAudioService).stopLoudnessCodecUpdates(eq(track.getPlayerIId()));
- } finally {
- mediaCodec1.release();
- mediaCodec2.release();
- }
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
public void removeAddedMediaCodecAfterSetTrack_callsAudioServiceRemoveCodec() throws Exception {
- final AudioTrack track = createAudioTrack();
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
mLcc.removeMediaCodec(mediaCodec);
- verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
} finally {
mediaCodec.release();
}
@@ -245,37 +177,28 @@ public class LoudnessCodecConfiguratorTest {
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void addMediaCodecAfterSetTrack_callsAudioServiceAdd() throws Exception {
- final AudioTrack track = createAudioTrack();
- final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
- final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
+ public void addMediaCodec_callsAudioServiceAdd() throws Exception {
+ final MediaCodec mediaCodec = createAndConfigureMediaCodec();
try {
- mLcc.addMediaCodec(mediaCodec1);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
-
- mLcc.addMediaCodec(mediaCodec2);
- verify(mAudioService).addLoudnessCodecInfo(eq(track.getPlayerIId()), anyInt(), any());
+ mLcc.addMediaCodec(mediaCodec);
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
} finally {
- mediaCodec1.release();
- mediaCodec2.release();
+ mediaCodec.release();
}
}
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void removeMediaCodecAfterSetTrack_callsAudioServiceRemove() throws Exception {
- final AudioTrack track = createAudioTrack();
+ public void removeMediaCodec_callsAudioServiceRemove() throws Exception {
final MediaCodec mediaCodec = createAndConfigureMediaCodec();
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
mLcc.removeMediaCodec(mediaCodec);
- verify(mAudioService).removeLoudnessCodecInfo(eq(track.getPlayerIId()), any());
+ verify(mAudioService).removeLoudnessCodecInfo(eq(mSessionId), any());
} finally {
mediaCodec.release();
}
@@ -283,15 +206,13 @@ public class LoudnessCodecConfiguratorTest {
@Test
@RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void removeWrongMediaCodecAfterSetTrack_triggersIAE() throws Exception {
- final AudioTrack track = createAudioTrack();
+ public void removeWrongMediaCodec_triggersIAE() throws Exception {
final MediaCodec mediaCodec1 = createAndConfigureMediaCodec();
final MediaCodec mediaCodec2 = createAndConfigureMediaCodec();
try {
mLcc.addMediaCodec(mediaCodec1);
- mLcc.setAudioTrack(track);
- verify(mAudioService).startLoudnessCodecUpdates(eq(track.getPlayerIId()), anyList());
+ verify(mAudioService).addLoudnessCodecInfo(eq(mSessionId), anyInt(), any());
assertThrows(IllegalArgumentException.class,
() -> mLcc.removeMediaCodec(mediaCodec2));
@@ -301,16 +222,6 @@ public class LoudnessCodecConfiguratorTest {
}
}
- private static AudioTrack createAudioTrack() {
- return new AudioTrack.Builder()
- .setAudioAttributes(new AudioAttributes.Builder().build())
- .setBufferSizeInBytes(TEST_AUDIO_TRACK_BUFFER_SIZE)
- .setAudioFormat(new AudioFormat.Builder()
- .setChannelMask(TEST_AUDIO_TRACK_CHANNELS)
- .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE).build())
- .build();
- }
-
private MediaCodec createAndConfigureMediaCodec() throws Exception {
AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
.getResources()
@@ -320,7 +231,7 @@ public class LoudnessCodecConfiguratorTest {
extractor = new MediaExtractor();
try {
extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
- testFd.getLength());
+ testFd.getLength());
assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
MediaFormat format = extractor.getTrackFormat(0);
String mime = format.getString(MediaFormat.KEY_MIME);
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 7573474f65b4..1046d8e9aebb 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -72,7 +72,7 @@ package android.nfc {
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
- method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
@@ -175,18 +175,18 @@ package android.nfc {
ctor public TagLostException(String);
}
- @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
- ctor public WlcLDeviceInfo(double, double, double, int);
+ @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcListenerDeviceInfo implements android.os.Parcelable {
+ ctor public WlcListenerDeviceInfo(int, double, double, int);
method public int describeContents();
- method public double getBatteryLevel();
- method public double getProductId();
+ method @FloatRange(from=0.0, to=100.0) public double getBatteryLevel();
+ method public int getProductId();
method public int getState();
method public double getTemperature();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CONNECTED_CHARGING = 2; // 0x2
- field public static final int CONNECTED_DISCHARGING = 3; // 0x3
- field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
- field public static final int DISCONNECTED = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcListenerDeviceInfo> CREATOR;
+ field public static final int STATE_CONNECTED_CHARGING = 2; // 0x2
+ field public static final int STATE_CONNECTED_DISCHARGING = 3; // 0x3
+ field public static final int STATE_DISCONNECTED = 1; // 0x1
}
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 40672a1adc15..dc2a625aacfd 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -8,7 +8,6 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
- method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
@@ -20,6 +19,7 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
@@ -37,7 +37,7 @@ package android.nfc {
}
@FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
- method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+ method public void onWlcStateChanged(@NonNull android.nfc.WlcListenerDeviceInfo);
}
}
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index bec62c5b1b82..63c3414acc36 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -32,8 +32,8 @@ import android.nfc.ITagRemovedCallback;
import android.nfc.INfcDta;
import android.nfc.INfcWlcStateListener;
import android.nfc.NfcAntennaInfo;
+import android.nfc.WlcListenerDeviceInfo;
import android.os.Bundle;
-import android.nfc.WlcLDeviceInfo;
/**
* @hide
@@ -90,11 +90,11 @@ interface INfcAdapter
boolean setObserveMode(boolean enabled);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
- boolean enableWlc(boolean enable);
+ boolean setWlcEnabled(boolean enable);
boolean isWlcEnabled();
void registerWlcStateListener(in INfcWlcStateListener listener);
void unregisterWlcStateListener(in INfcWlcStateListener listener);
- WlcLDeviceInfo getWlcLDeviceInfo();
+ WlcListenerDeviceInfo getWlcListenerDeviceInfo();
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
diff --git a/nfc/java/android/nfc/INfcWlcStateListener.aidl b/nfc/java/android/nfc/INfcWlcStateListener.aidl
index c2b7075bc6e4..584eb9a128b4 100644
--- a/nfc/java/android/nfc/INfcWlcStateListener.aidl
+++ b/nfc/java/android/nfc/INfcWlcStateListener.aidl
@@ -16,7 +16,7 @@
package android.nfc;
-import android.nfc.WlcLDeviceInfo;
+import android.nfc.WlcListenerDeviceInfo;
/**
* @hide
*/
@@ -24,7 +24,7 @@ oneway interface INfcWlcStateListener {
/**
* Called whenever NFC WLC state changes
*
- * @param wlcLDeviceInfo NFC wlc listener information
+ * @param wlcListenerDeviceInfo NFC wlc listener information
*/
- void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
+ void onWlcStateChanged(in WlcListenerDeviceInfo wlcListenerDeviceInfo);
}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 68c16e69f3af..11eb97b440bf 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2820,13 +2820,12 @@ public final class NfcAdapter {
@SystemApi
@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public boolean enableWlc(boolean enable) {
+ public boolean setWlcEnabled(boolean enable) {
if (!sHasNfcWlcFeature) {
throw new UnsupportedOperationException();
}
try {
- return sService.enableWlc(enable);
-
+ return sService.setWlcEnabled(enable);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
@@ -2835,7 +2834,7 @@ public final class NfcAdapter {
return false;
}
try {
- return sService.enableWlc(enable);
+ return sService.setWlcEnabled(enable);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover NFC Service.");
}
@@ -2887,7 +2886,7 @@ public final class NfcAdapter {
/**
* Called on NFC WLC state changes
*/
- void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+ void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo);
}
/**
@@ -2945,12 +2944,12 @@ public final class NfcAdapter {
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
@Nullable
- public WlcLDeviceInfo getWlcLDeviceInfo() {
+ public WlcListenerDeviceInfo getWlcListenerDeviceInfo() {
if (!sHasNfcWlcFeature) {
throw new UnsupportedOperationException();
}
try {
- return sService.getWlcLDeviceInfo();
+ return sService.getWlcListenerDeviceInfo();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
@@ -2959,7 +2958,7 @@ public final class NfcAdapter {
return null;
}
try {
- return sService.getWlcLDeviceInfo();
+ return sService.getWlcListenerDeviceInfo();
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover NFC Service.");
}
diff --git a/nfc/java/android/nfc/NfcWlcStateListener.java b/nfc/java/android/nfc/NfcWlcStateListener.java
index 8d793101f41f..890cb090f587 100644
--- a/nfc/java/android/nfc/NfcWlcStateListener.java
+++ b/nfc/java/android/nfc/NfcWlcStateListener.java
@@ -36,7 +36,7 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
- private WlcLDeviceInfo mCurrentState = null;
+ private WlcListenerDeviceInfo mCurrentState = null;
private boolean mIsRegistered = false;
public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
@@ -98,8 +98,10 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
Executor executor = mListenerMap.get(listener);
final long identity = Binder.clearCallingIdentity();
try {
- executor.execute(() -> listener.onWlcStateChanged(
- mCurrentState));
+ if (Flags.enableNfcCharging()) {
+ executor.execute(() -> listener.onWlcStateChanged(
+ mCurrentState));
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -107,9 +109,9 @@ public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
}
@Override
- public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+ public void onWlcStateChanged(@NonNull WlcListenerDeviceInfo wlcListenerDeviceInfo) {
synchronized (this) {
- mCurrentState = wlcLDeviceInfo;
+ mCurrentState = wlcListenerDeviceInfo;
for (WlcStateListener cb : mListenerMap.keySet()) {
sendCurrentState(cb);
diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.java b/nfc/java/android/nfc/WlcLDeviceInfo.java
deleted file mode 100644
index 016431e90d8e..000000000000
--- a/nfc/java/android/nfc/WlcLDeviceInfo.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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 android.nfc;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Contains information of the nfc wireless charging listener device information.
- */
-@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
-public final class WlcLDeviceInfo implements Parcelable {
- public static final int DISCONNECTED = 1;
-
- public static final int CONNECTED_CHARGING = 2;
-
- public static final int CONNECTED_DISCHARGING = 3;
-
- private double mProductId;
- private double mTemperature;
- private double mBatteryLevel;
- private int mState;
-
- public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
- this.mProductId = productId;
- this.mTemperature = temperature;
- this.mBatteryLevel = batteryLevel;
- this.mState = state;
- }
-
- /**
- * ProductId of the WLC listener device.
- */
- public double getProductId() {
- return mProductId;
- }
-
- /**
- * Temperature of the WLC listener device.
- */
- public double getTemperature() {
- return mTemperature;
- }
-
- /**
- * BatteryLevel of the WLC listener device.
- */
- public double getBatteryLevel() {
- return mBatteryLevel;
- }
-
- /**
- * State of the WLC listener device.
- */
- public int getState() {
- return mState;
- }
-
- private WlcLDeviceInfo(Parcel in) {
- this.mProductId = in.readDouble();
- this.mTemperature = in.readDouble();
- this.mBatteryLevel = in.readDouble();
- this.mState = in.readInt();
- }
-
- public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
- new Parcelable.Creator<WlcLDeviceInfo>() {
- @Override
- public WlcLDeviceInfo createFromParcel(Parcel in) {
- return new WlcLDeviceInfo(in);
- }
-
- @Override
- public WlcLDeviceInfo[] newArray(int size) {
- return new WlcLDeviceInfo[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeDouble(mProductId);
- dest.writeDouble(mTemperature);
- dest.writeDouble(mBatteryLevel);
- dest.writeDouble(mState);
- }
-}
diff --git a/nfc/java/android/nfc/WlcLDeviceInfo.aidl b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl
index 33143fe81162..7f2ca545007b 100644
--- a/nfc/java/android/nfc/WlcLDeviceInfo.aidl
+++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.aidl
@@ -16,4 +16,4 @@
package android.nfc;
-parcelable WlcLDeviceInfo;
+parcelable WlcListenerDeviceInfo;
diff --git a/nfc/java/android/nfc/WlcListenerDeviceInfo.java b/nfc/java/android/nfc/WlcListenerDeviceInfo.java
new file mode 100644
index 000000000000..45315f812250
--- /dev/null
+++ b/nfc/java/android/nfc/WlcListenerDeviceInfo.java
@@ -0,0 +1,145 @@
+/*
+ * 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 android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcListenerDeviceInfo implements Parcelable {
+ /**
+ * Device is currently not connected with any WlcListenerDevice.
+ */
+ public static final int STATE_DISCONNECTED = 1;
+
+ /**
+ * Device is currently connected with a WlcListenerDevice and is charging it.
+ */
+ public static final int STATE_CONNECTED_CHARGING = 2;
+
+ /**
+ * Device is currently connected with a WlcListenerDevice without charging it.
+ */
+ public static final int STATE_CONNECTED_DISCHARGING = 3;
+
+ /**
+ * Possible states from {@link #getState}.
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_DISCONNECTED,
+ STATE_CONNECTED_CHARGING,
+ STATE_CONNECTED_DISCHARGING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WlcListenerState{}
+
+ private int mProductId;
+ private double mTemperature;
+ private double mBatteryLevel;
+ private int mState;
+
+ /**
+ * Create a new object containing wlc listener information.
+ *
+ * @param productId code for the device vendor
+ * @param temperature current temperature
+ * @param batteryLevel current battery level
+ * @param state current state
+ */
+ public WlcListenerDeviceInfo(int productId, double temperature, double batteryLevel,
+ @WlcListenerState int state) {
+ this.mProductId = productId;
+ this.mTemperature = temperature;
+ this.mBatteryLevel = batteryLevel;
+ this.mState = state;
+ }
+
+ /**
+ * ProductId of the WLC listener device.
+ * @return integer that is converted from USI Stylus VendorID[11:0].
+ */
+ public int getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Temperature of the WLC listener device.
+ * @return the value represents the temperature in °C.
+ */
+ public double getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * BatteryLevel of the WLC listener device.
+ * @return battery level in percentage [0-100]
+ */
+ public @FloatRange(from = 0.0, to = 100.0) double getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ /**
+ * State of the WLC listener device.
+ */
+ public @WlcListenerState int getState() {
+ return mState;
+ }
+
+ private WlcListenerDeviceInfo(Parcel in) {
+ this.mProductId = in.readInt();
+ this.mTemperature = in.readDouble();
+ this.mBatteryLevel = in.readDouble();
+ this.mState = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<WlcListenerDeviceInfo> CREATOR =
+ new Parcelable.Creator<WlcListenerDeviceInfo>() {
+ @Override
+ public WlcListenerDeviceInfo createFromParcel(Parcel in) {
+ return new WlcListenerDeviceInfo(in);
+ }
+
+ @Override
+ public WlcListenerDeviceInfo[] newArray(int size) {
+ return new WlcListenerDeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mProductId);
+ dest.writeDouble(mTemperature);
+ dest.writeDouble(mBatteryLevel);
+ dest.writeInt(mState);
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index ba8e354fa0c6..b34c310cd30f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -54,16 +54,18 @@ fun SettingsExposedDropdownMenuCheckBox(
selectedOptionsState: SnapshotStateList<Int>,
emptyVal: String = "",
enabled: Boolean,
+ errorMessage: String? = null,
onSelectedOptionStateChange: () -> Unit,
) {
var dropDownWidth by remember { mutableIntStateOf(0) }
var expanded by remember { mutableStateOf(false) }
+ val allIndex = options.indexOf("*")
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier
.width(350.dp)
- .padding(SettingsDimension.menuFieldPadding)
+ .padding(SettingsDimension.textFieldPadding)
.onSizeChanged { dropDownWidth = it.width },
) {
OutlinedTextField(
@@ -72,7 +74,8 @@ fun SettingsExposedDropdownMenuCheckBox(
.menuAnchor()
.fillMaxWidth(),
value = if (selectedOptionsState.size == 0) emptyVal
- else selectedOptionsState.joinToString { options[it] },
+ else if (selectedOptionsState.contains(allIndex)) "*"
+ else selectedOptionsState.joinToString { options[it] },
onValueChange = {},
label = { Text(text = label) },
trailingIcon = {
@@ -81,7 +84,13 @@ fun SettingsExposedDropdownMenuCheckBox(
)
},
readOnly = true,
- enabled = enabled
+ enabled = enabled,
+ isError = errorMessage != null,
+ supportingText = {
+ if (errorMessage != null) {
+ Text(text = errorMessage)
+ }
+ }
)
if (options.isNotEmpty()) {
ExposedDropdownMenu(
@@ -98,9 +107,17 @@ fun SettingsExposedDropdownMenuCheckBox(
.fillMaxWidth(),
onClick = {
if (selectedOptionsState.contains(index)) {
- selectedOptionsState.remove(
- index
- )
+ if (index == allIndex)
+ selectedOptionsState.clear()
+ else {
+ selectedOptionsState.remove(
+ index
+ )
+ if (selectedOptionsState.contains(allIndex))
+ selectedOptionsState.remove(
+ allIndex
+ )
+ }
} else {
selectedOptionsState.add(
index
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 57012aabb123..931a6f149b84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -3,17 +3,17 @@
*/
/* 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.
-*/
+ * 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.bluetooth;
@@ -23,6 +23,7 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
@@ -30,6 +31,7 @@ import android.content.Context;
import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -57,13 +59,12 @@ public class LeAudioProfile implements LocalBluetoothProfile {
private static final int ORDINAL = 1;
// These callbacks run on the main thread.
- private final class LeAudioServiceListener
- implements BluetoothProfile.ServiceListener {
+ private final class LeAudioServiceListener implements BluetoothProfile.ServiceListener {
@RequiresApi(Build.VERSION_CODES.S)
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (DEBUG) {
- Log.d(TAG,"Bluetooth service connected");
+ Log.d(TAG, "Bluetooth service connected");
}
mService = (BluetoothLeAudio) proxy;
// We just bound to the service, so refresh the UI for any connected LeAudio devices.
@@ -78,8 +79,7 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
device = mDeviceManager.addDevice(nextDevice);
}
- device.onProfileStateChanged(LeAudioProfile.this,
- BluetoothProfile.STATE_CONNECTED);
+ device.onProfileStateChanged(LeAudioProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
@@ -89,7 +89,7 @@ public class LeAudioProfile implements LocalBluetoothProfile {
public void onServiceDisconnected(int profile) {
if (DEBUG) {
- Log.d(TAG,"Bluetooth service disconnected");
+ Log.d(TAG, "Bluetooth service disconnected");
}
mProfileManager.callServiceDisconnectedListeners();
mIsProfileReady = false;
@@ -105,7 +105,9 @@ public class LeAudioProfile implements LocalBluetoothProfile {
return BluetoothProfile.LE_AUDIO;
}
- LeAudioProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LeAudioProfile(
+ Context context,
+ CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
mDeviceManager = deviceManager;
@@ -113,8 +115,7 @@ public class LeAudioProfile implements LocalBluetoothProfile {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.getProfileProxy(
- context, new LeAudioServiceListener(),
- BluetoothProfile.LE_AUDIO);
+ context, new LeAudioServiceListener(), BluetoothProfile.LE_AUDIO);
}
public boolean accessProfileEnabled() {
@@ -126,18 +127,22 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
public List<BluetoothDevice> getConnectedDevices() {
- return getDevicesByStates(new int[] {
- BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ return getDevicesByStates(
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
public List<BluetoothDevice> getConnectableDevices() {
- return getDevicesByStates(new int[] {
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ return getDevicesByStates(
+ new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
private List<BluetoothDevice> getDevicesByStates(int[] states) {
@@ -148,8 +153,8 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
/*
- * @hide
- */
+ * @hide
+ */
public boolean connect(BluetoothDevice device) {
if (mService == null) {
return false;
@@ -158,8 +163,8 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
/*
- * @hide
- */
+ * @hide
+ */
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
@@ -174,6 +179,14 @@ public class LeAudioProfile implements LocalBluetoothProfile {
return mService.getConnectionState(device);
}
+ /** Get group id for {@link BluetoothDevice}. */
+ public int getGroupId(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+ return mService.getGroupId(device);
+ }
+
public boolean setActiveDevice(BluetoothDevice device) {
if (mBluetoothAdapter == null) {
return false;
@@ -193,29 +206,28 @@ public class LeAudioProfile implements LocalBluetoothProfile {
/**
* Get Lead device for the group.
*
- * Lead device is the device that can be used as an active device in the system.
- * Active devices points to the Audio Device for the Le Audio group.
- * This method returns the Lead devices for the connected LE Audio
- * group and this device should be used in the setActiveDevice() method by other parts
- * of the system, which wants to set to active a particular Le Audio group.
+ * <p>Lead device is the device that can be used as an active device in the system. Active
+ * devices points to the Audio Device for the Le Audio group. This method returns the Lead
+ * devices for the connected LE Audio group and this device should be used in the
+ * setActiveDevice() method by other parts of the system, which wants to set to active a
+ * particular Le Audio group.
*
- * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
+ * <p>Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
* Note: When Lead device gets disconnected while Le Audio group is active and has more devices
- * in the group, then Lead device will not change. If Lead device gets disconnected, for the
- * Le Audio group which is not active, a new Lead device will be chosen
+ * in the group, then Lead device will not change. If Lead device gets disconnected, for the Le
+ * Audio group which is not active, a new Lead device will be chosen
*
* @param groupId The group id.
* @return group lead device.
- *
* @hide
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
if (DEBUG) {
- Log.d(TAG,"getConnectedGroupLeadDevice");
+ Log.d(TAG, "getConnectedGroupLeadDevice");
}
if (mService == null) {
- Log.e(TAG,"No service.");
+ Log.e(TAG, "No service.");
return null;
}
return mService.getConnectedGroupLeadDevice(groupId);
@@ -310,10 +322,10 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
if (mService != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.LE_AUDIO,
- mService);
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.LE_AUDIO, mService);
mService = null;
- }catch (Throwable t) {
+ } catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index de21c541c7e4..934870507a20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -84,6 +84,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
+ Settings.Secure.getUriFor(
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
};
private BluetoothLeBroadcast mServiceBroadcast;
@@ -96,6 +98,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private String mNewAppSourceName = "";
private boolean mIsBroadcastProfileReady = false;
private boolean mIsBroadcastAssistantProfileReady = false;
+ private boolean mImproveCompatibility = false;
private String mProgramInfo;
private byte[] mBroadcastCode;
private Executor mExecutor;
@@ -391,6 +394,52 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
* <p>If the system started the LE Broadcast, then the system calls the corresponding callback
* {@link BluetoothLeBroadcast.Callback}.
*/
+ public void startPrivateBroadcast() {
+ mNewAppSourceName = "Sharing audio";
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
+ return;
+ }
+ if (mServiceBroadcast.getAllBroadcastMetadata().size()
+ >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
+ Log.d(TAG, "Skip starting the broadcast due to number limit.");
+ return;
+ }
+ String programInfo = getProgramInfo();
+ boolean improveCompatibility = getImproveCompatibility();
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "startBroadcast: language = null , programInfo = "
+ + programInfo
+ + ", improveCompatibility = "
+ + improveCompatibility);
+ }
+ // Current broadcast framework only support one subgroup
+ BluetoothLeBroadcastSubgroupSettings subgroupSettings =
+ buildBroadcastSubgroupSettings(
+ /* language= */ null, programInfo, improveCompatibility);
+ BluetoothLeBroadcastSettings settings =
+ buildBroadcastSettings(
+ true, // TODO: set to false after framework fix
+ TextUtils.isEmpty(programInfo) ? null : programInfo,
+ (mBroadcastCode != null && mBroadcastCode.length > 0)
+ ? mBroadcastCode
+ : null,
+ ImmutableList.of(subgroupSettings));
+ mServiceBroadcast.startBroadcast(settings);
+ }
+
+ /**
+ * Start the private Broadcast for personal audio sharing or qr code sharing.
+ *
+ * <p>The broadcast will use random string for both broadcast name and subgroup program info;
+ * The broadcast will use random string for broadcast code; The broadcast will only have one
+ * subgroup due to system limitation; The subgroup language will be null.
+ *
+ * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
+ * {@link BluetoothLeBroadcast.Callback}.
+ */
public void startPrivateBroadcast(int quality) {
mNewAppSourceName = "Sharing audio";
if (mServiceBroadcast == null) {
@@ -408,7 +457,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
// Current broadcast framework only support one subgroup
BluetoothLeBroadcastSubgroupSettings subgroupSettings =
- buildBroadcastSubgroupSettings(/* language= */ null, programInfo, quality);
+ buildBroadcastSubgroupSettings(
+ /* language= */ null,
+ programInfo,
+ /* improveCompatibility= */
+ BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD == quality);
BluetoothLeBroadcastSettings settings =
buildBroadcastSettings(
true, // TODO: set to false after framework fix
@@ -437,7 +490,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings(
- @Nullable String language, @Nullable String programInfo, int quality) {
+ @Nullable String language, @Nullable String programInfo, boolean improveCompatibility) {
BluetoothLeAudioContentMetadata metadata =
new BluetoothLeAudioContentMetadata.Builder()
.setLanguage(language)
@@ -447,7 +500,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
// metadata to keep legacy UI working.
mBluetoothLeAudioContentMetadata = metadata;
return new BluetoothLeBroadcastSubgroupSettings.Builder()
- .setPreferredQuality(quality)
+ .setPreferredQuality(
+ improveCompatibility
+ ? BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD
+ : BluetoothLeBroadcastSubgroupSettings.QUALITY_HIGH)
.setContentMetadata(mBluetoothLeAudioContentMetadata)
.build();
}
@@ -513,6 +569,36 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
}
+ /** Get compatibility config for broadcast. */
+ public boolean getImproveCompatibility() {
+ return mImproveCompatibility;
+ }
+
+ /** Set compatibility config for broadcast. */
+ public void setImproveCompatibility(boolean improveCompatibility) {
+ setImproveCompatibility(improveCompatibility, /* updateContentResolver= */ true);
+ }
+
+ private void setImproveCompatibility(
+ boolean improveCompatibility, boolean updateContentResolver) {
+ if (mImproveCompatibility == improveCompatibility) {
+ Log.d(TAG, "setImproveCompatibility: improveCompatibility is not changed");
+ return;
+ }
+ mImproveCompatibility = improveCompatibility;
+ if (updateContentResolver) {
+ if (mContentResolver == null) {
+ Log.d(TAG, "mContentResolver is null");
+ return;
+ }
+ Log.d(TAG, "Set improveCompatibility to: " + improveCompatibility);
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ improveCompatibility ? "1" : "0");
+ }
+ }
+
private void setLatestBroadcastId(int broadcastId) {
Log.d(TAG, "setLatestBroadcastId: mBroadcastId is " + broadcastId);
mBroadcastId = broadcastId;
@@ -600,6 +686,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Settings.Secure.getString(
mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
setAppSourceName(appSourceName, /* updateContentResolver= */ false);
+
+ String improveCompatibility =
+ Settings.Secure.getString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY);
+ setImproveCompatibility(
+ improveCompatibility == null ? false : improveCompatibility.equals("1"),
+ /* updateContentResolver= */ false);
}
private void updateBroadcastInfoFromBroadcastMetadata(
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index d6f1eab442fa..15f33d2cff42 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -229,6 +229,17 @@ public class PhoneMediaDevice extends MediaDevice {
@SuppressWarnings("NewApi")
@Override
public String getId() {
+ if (com.android.media.flags.Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ // Note: be careful when removing this flag. Instead of just removing it, you might want
+ // to replace it with SDK_INT >= 35. Explanation: The presence of SDK checks in settings
+ // lib suggests that a mainline component may depend on this code. Which means removing
+ // this "if" (and using always the route info id) could mean a regression on mainline
+ // code running on a device that's running API 34 or older. Unfortunately, we cannot
+ // check the API level at the moment of writing this code because the API level has not
+ // been bumped, yet.
+ return mRouteInfo.getId();
+ }
+
String id;
switch (mRouteInfo.getType()) {
case TYPE_WIRED_HEADSET:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 1746befbfa4d..ceba9be70487 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -31,10 +31,15 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.media.MediaRoute2Info;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.media.flags.Flags;
import com.android.settingslib.R;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +50,8 @@ import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class PhoneMediaDeviceTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private MediaRoute2Info mInfo;
@@ -110,8 +117,18 @@ public class PhoneMediaDeviceTest {
.isEqualTo(mContext.getString(R.string.media_transfer_this_device_name));
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+ @Test
+ public void getId_whenAdvancedWiredRoutingEnabled_returnCorrectId() {
+ String fakeId = "foo";
+ when(mInfo.getId()).thenReturn(fakeId);
+
+ assertThat(mPhoneMediaDevice.getId()).isEqualTo(fakeId);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
@Test
- public void getId_returnCorrectId() {
+ public void getId_whenAdvancedWiredRoutingDisabled_returnCorrectId() {
when(mInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
assertThat(mPhoneMediaDevice.getId())
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2d442f4c0e6e..3a46f4e96ccb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -406,7 +406,7 @@ public class SettingsProvider extends ContentProvider {
Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
- mSettingsRegistry = new SettingsRegistry();
+ mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper());
}
SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext());
synchronized (mLock) {
@@ -2896,8 +2896,8 @@ public class SettingsProvider extends ContentProvider {
private String mSettingsCreationBuildId;
- public SettingsRegistry() {
- mHandler = new MyHandler(getContext().getMainLooper());
+ SettingsRegistry(Looper looper) {
+ mHandler = new MyHandler(looper);
mGenerationRegistry = new GenerationRegistry(UserManager.getMaxSupportedUsers());
mBackupManager = new BackupManager(getContext());
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 323613077a70..2c35c777ab12 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -89,6 +89,13 @@ flag {
}
flag {
+ name: "notification_avalanche_suppression"
+ namespace: "systemui"
+ description: "After notification avalanche floodgate event, suppress HUNs completely."
+ bug: "321089634"
+}
+
+flag {
name: "notification_background_tint_optimization"
namespace: "systemui"
description: "Re-enable the codepath that removed tinting of notifications when the"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
index ccf119ab3088..97c407cb9b16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -41,10 +41,10 @@ import org.junit.runner.RunWith
class FingerprintPropertyInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val underTest = kosmos.fingerprintPropertyInteractor
- private val repository = kosmos.fingerprintPropertyRepository
- private val configurationRepository = kosmos.fakeConfigurationRepository
- private val displayRepository = kosmos.displayRepository
+ private val underTest by lazy { kosmos.fingerprintPropertyInteractor }
+ private val repository by lazy { kosmos.fingerprintPropertyRepository }
+ private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val displayRepository by lazy { kosmos.displayRepository }
@Test
fun sensorLocation_resolution1f() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index c8560c31cdf1..c300e0a905b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -55,7 +55,9 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
- private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private val mainHandler by lazy {
+ FakeHandler(Looper.getMainLooper())
+ }
private lateinit var underTest: PrimaryBouncerInteractor
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 27b84b2ffabc..d30e33332926 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -39,8 +39,8 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val bouncerInteractor = kosmos.bouncerInteractor
- private val underTest =
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val underTest by lazy {
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
@@ -49,6 +49,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
+ }
@Test
fun animateFailure() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index cfe8c5d52c18..73db1757c06a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -58,8 +58,8 @@ class BouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor = kosmos.authenticationInteractor
- private val bouncerInteractor = kosmos.bouncerInteractor
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private lateinit var underTest: BouncerViewModel
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index a0c2acc31589..cddbd1f78dd5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -66,7 +66,9 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() {
@Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
lateinit var bouncerInteractor: PrimaryBouncerInteractor
- private val mainHandler = FakeHandler(Looper.getMainLooper())
+ private val mainHandler by lazy {
+ FakeHandler(Looper.getMainLooper())
+ }
val repository = FakeKeyguardBouncerRepository()
lateinit var underTest: KeyguardBouncerViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index b3b6457b46e7..c6d612d5dc79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -52,17 +52,18 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationInteractor = kosmos.authenticationInteractor
- private val sceneInteractor = kosmos.sceneInteractor
- private val bouncerInteractor = kosmos.bouncerInteractor
- private val bouncerViewModel = kosmos.bouncerViewModel
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
private val isInputEnabled = MutableStateFlow(true)
- private val underTest =
+ private val underTest by lazy {
PasswordBouncerViewModel(
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled.asStateFlow(),
)
+ }
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index c2680bcc82a3..725bdbd43445 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -53,17 +53,18 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor = kosmos.authenticationInteractor
- private val sceneInteractor = kosmos.sceneInteractor
- private val bouncerInteractor = kosmos.bouncerInteractor
- private val bouncerViewModel = kosmos.bouncerViewModel
- private val underTest =
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
+ private val underTest by lazy {
PatternBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
)
+ }
private val containerSize = 90 // px
private val dotSize = 30 // px
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 1d660d63710d..06e12586d384 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -53,22 +53,24 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val authenticationInteractor = kosmos.authenticationInteractor
- private val bouncerInteractor = kosmos.bouncerInteractor
- private val bouncerViewModel = kosmos.bouncerViewModel
- private val underTest =
- PinBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = kosmos.simBouncerInteractor,
- authenticationMethod = AuthenticationMethodModel.Pin,
- )
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
+ private lateinit var underTest: PinBouncerViewModel
@Before
fun setUp() {
+ underTest =
+ PinBouncerViewModel(
+ applicationContext = context,
+ viewModelScope = testScope.backgroundScope,
+ interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ simBouncerInteractor = kosmos.simBouncerInteractor,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ )
+
overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index ed29aa4ac202..e8216735fb5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepositor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.communalInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 21190608e002..86279ef24ca7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
+import android.content.pm.UserInfo
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,12 +43,12 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.communalInteractor
-import com.android.systemui.keyguard.domain.interactor.editWidgetsActivityStarter
import com.android.systemui.kosmos.testScope
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -59,8 +60,10 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
/**
* This class of test cases assume that communal is enabled. For disabled cases, see
@@ -70,6 +73,9 @@ import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorTest : SysuiTestCase() {
+ @Mock private lateinit var mainUser: UserInfo
+ @Mock private lateinit var secondaryUser: UserInfo
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -78,6 +84,7 @@ class CommunalInteractorTest : SysuiTestCase() {
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
+ private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
@@ -86,15 +93,22 @@ class CommunalInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
tutorialRepository = kosmos.fakeCommunalTutorialRepository
communalRepository = kosmos.fakeCommunalRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
widgetRepository = kosmos.fakeCommunalWidgetRepository
smartspaceRepository = kosmos.fakeSmartspaceRepository
+ userRepository = kosmos.fakeUserRepository
keyguardRepository = kosmos.fakeKeyguardRepository
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
+ whenever(mainUser.isMain).thenReturn(true)
+ whenever(secondaryUser.isMain).thenReturn(false)
+ userRepository.setUserInfos(listOf(mainUser, secondaryUser))
+
underTest = kosmos.communalInteractor
}
@@ -103,36 +117,52 @@ class CommunalInteractorTest : SysuiTestCase() {
testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
@Test
- fun isCommunalAvailable_trueWhenStorageUnlock() =
+ fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
testScope.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
runCurrent()
assertThat(isAvailable).isTrue()
}
@Test
- fun isCommunalAvailable_whenStorageUnlock_true() =
+ fun isCommunalAvailable_storageLockedAndMainUser_false() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(true)
+ userRepository.setSelectedUserInfo(mainUser)
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+
+ @Test
+ fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() =
testScope.runTest {
val isAvailable by collectLastValue(underTest.isCommunalAvailable)
assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(secondaryUser)
runCurrent()
- assertThat(isAvailable).isTrue()
+ assertThat(isAvailable).isFalse()
}
@Test
- fun updateAppWidgetHostActive_uponStorageUnlock_true() =
+ fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
testScope.runTest {
collectLastValue(underTest.isCommunalAvailable)
assertThat(widgetRepository.isHostActive()).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
+ userRepository.setSelectedUserInfo(mainUser)
runCurrent()
assertThat(widgetRepository.isHostActive()).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 161356d1bed2..352463f5a198 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.domain.interactor
+import android.content.pm.UserInfo
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
@@ -30,9 +31,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.settings.UserTracker
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -45,15 +46,17 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
+ @Mock lateinit var user: UserInfo
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- @Mock private lateinit var userTracker: UserTracker
-
private lateinit var underTest: CommunalTutorialInteractor
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var communalInteractor: CommunalInteractor
+ private lateinit var userRepository: FakeUserRepository
@Before
fun setUp() {
@@ -62,16 +65,17 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
communalRepository = kosmos.fakeCommunalRepository
+ communalInteractor = kosmos.communalInteractor
+ userRepository = kosmos.fakeUserRepository
underTest = kosmos.communalTutorialInteractor
-
- whenever(userTracker.userHandle).thenReturn(mock())
}
@Test
fun tutorialUnavailable_whenKeyguardNotVisible() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
keyguardRepository.setKeyguardShowing(false)
assertThat(isTutorialAvailable).isFalse()
@@ -81,6 +85,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
fun tutorialUnavailable_whenTutorialIsCompleted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(false)
@@ -89,9 +94,20 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
}
@Test
+ fun tutorialUnavailable_whenCommunalNotAvailable() =
+ testScope.runTest {
+ val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(false)
+ communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
+ keyguardRepository.setKeyguardShowing(true)
+ assertThat(isTutorialAvailable).isFalse()
+ }
+
+ @Test
fun tutorialAvailable_whenTutorialNotStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(false)
@@ -103,6 +119,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
fun tutorialAvailable_whenTutorialIsStarted() =
testScope.runTest {
val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable)
+ setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
communalRepository.setIsCommunalHubShowing(true)
@@ -183,4 +200,16 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
+
+ private suspend fun setCommunalAvailable(available: Boolean) {
+ if (available) {
+ communalRepository.setIsCommunalEnabled(true)
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ whenever(user.isMain).thenReturn(true)
+ userRepository.setUserInfos(listOf(user))
+ userRepository.setSelectedUserInfo(user)
+ } else {
+ keyguardRepository.setIsEncryptedOrLockdown(true)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
index 721fc4906aba..6b1b93777fbc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt
@@ -21,14 +21,15 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -47,17 +48,16 @@ import org.mockito.MockitoAnnotations
class CommunalLoggerStartableTest : SysuiTestCase() {
@Mock private lateinit var uiEventLogger: UiEventLogger
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
private lateinit var communalInteractor: CommunalInteractor
private lateinit var underTest: CommunalLoggerStartable
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- val withDeps = CommunalInteractorFactory.create()
- testScope = withDeps.testScope
- communalInteractor = withDeps.communalInteractor
+ communalInteractor = kosmos.communalInteractor
underTest =
CommunalLoggerStartable(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 867a48dc3925..a2dec5ff8830 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -24,19 +24,21 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -59,8 +61,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var communalRepository: FakeCommunalRepository
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
@@ -72,17 +72,14 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- val withDeps = CommunalInteractorFactory.create(testScope)
- keyguardRepository = withDeps.keyguardRepository
- communalRepository = withDeps.communalRepository
- tutorialRepository = withDeps.tutorialRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- mediaRepository = withDeps.mediaRepository
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
underTest =
CommunalEditModeViewModel(
- withDeps.communalInteractor,
+ kosmos.communalInteractor,
mediaHost,
uiEventLogger,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9ac21dd69aab..c814f3f1db6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,31 +17,39 @@
package com.android.systemui.communal.view.viewmodel
import android.app.smartspace.SmartspaceTarget
+import android.content.pm.UserInfo
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -57,16 +65,17 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class CommunalViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var user: UserInfo
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var communalRepository: FakeCommunalRepository
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
- private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
+ private lateinit var userRepository: FakeUserRepository
private lateinit var underTest: CommunalViewModel
@@ -74,22 +83,18 @@ class CommunalViewModelTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
-
- val withDeps = CommunalInteractorFactory.create()
- keyguardRepository = withDeps.keyguardRepository
- communalRepository = withDeps.communalRepository
- tutorialRepository = withDeps.tutorialRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- mediaRepository = withDeps.mediaRepository
- communalPrefsRepository = withDeps.communalPrefsRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
+ userRepository = kosmos.fakeUserRepository
underTest =
CommunalViewModel(
testScope,
- withDeps.communalInteractor,
- withDeps.tutorialInteractor,
+ kosmos.communalInteractor,
+ kosmos.communalTutorialInteractor,
mediaHost,
)
}
@@ -104,9 +109,11 @@ class CommunalViewModelTest : SysuiTestCase() {
@Test
fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
testScope.runTest {
- // Keyguard showing, and tutorial not started.
+ // Keyguard showing, storage unlocked, main user, and tutorial not started.
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ setIsMainUser(true)
tutorialRepository.setTutorialSettingState(
Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
)
@@ -202,4 +209,10 @@ class CommunalViewModelTest : SysuiTestCase() {
underTest.onHidePopupAfterDismissCta()
assertThat(isPopupOnDismissCtaShowing).isEqualTo(false)
}
+
+ private suspend fun setIsMainUser(isMainUser: Boolean) {
+ whenever(user.isMain).thenReturn(isMainUser)
+ userRepository.setUserInfos(listOf(user))
+ userRepository.setSelectedUserInfo(user)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 52305b1f5212..05b589126b08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -49,10 +49,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
- private val trustRepository = kosmos.fakeTrustRepository
- private val sceneInteractor = kosmos.sceneInteractor
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+ private val trustRepository by lazy { kosmos.fakeTrustRepository }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private lateinit var underTest: DeviceEntryInteractor
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 562f96c28b19..c14346899ede 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -93,7 +93,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
- WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
+ WindowManager.LayoutParams mWindowParams;
@Mock
IDreamOverlayCallback mDreamOverlayCallback;
@@ -184,6 +184,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
+ mWindowParams = new WindowManager.LayoutParams();
mService = new DreamOverlayService(
mContext,
mLifecycleOwner,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6f62afc560fe..dc8b97abbfe8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -139,6 +139,18 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun topClippingBounds() =
+ testScope.runTest {
+ assertThat(underTest.topClippingBounds.value).isNull()
+
+ underTest.topClippingBounds.value = 50
+ assertThat(underTest.topClippingBounds.value).isEqualTo(50)
+
+ underTest.topClippingBounds.value = 500
+ assertThat(underTest.topClippingBounds.value).isEqualTo(500)
+ }
+
+ @Test
fun clockPosition() =
testScope.runTest {
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 2c3afb1b40a9..0b320a28b419 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -54,15 +54,17 @@ class KeyguardInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardRepository
- private val sceneInteractor = kosmos.sceneInteractor
- private val commandQueue = FakeCommandQueue()
+ private val repository by lazy { kosmos.fakeKeyguardRepository }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val commandQueue by lazy {
+ FakeCommandQueue()
+ }
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val shadeRepository = FakeShadeRepository()
private val transitionState: MutableStateFlow<ObservableTransitionState> =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone))
- private val underTest =
+ private val underTest by lazy {
KeyguardInteractor(
repository = repository,
commandQueue = commandQueue,
@@ -73,6 +75,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository = shadeRepository,
sceneInteractorProvider = { sceneInteractor },
)
+ }
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 9bccf4e26e87..ce43d4e14cc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -38,12 +38,12 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.Spy
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@@ -51,16 +51,19 @@ import org.mockito.Spy
class LightRevealScrimInteractorTest : SysuiTestCase() {
private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
- @Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+ private val fakeLightRevealScrimRepository by lazy {
+ Mockito.spy(FakeLightRevealScrimRepository())
+ }
private val testScope = TestScope()
- private val keyguardTransitionInteractor =
+ private val keyguardTransitionInteractor by lazy {
KeyguardTransitionInteractorFactory.create(
scope = testScope.backgroundScope,
repository = fakeKeyguardTransitionRepository,
)
.keyguardTransitionInteractor
+ }
private lateinit var underTest: LightRevealScrimInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index 9daf1860ebb8..199ffa6b87bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -20,11 +20,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -36,6 +39,7 @@ import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,10 +49,18 @@ import org.junit.runner.RunWith
class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
- private val biometricSettingsRepository = kosmos.biometricSettingsRepository
- private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var underTest: AlternateBouncerToAodTransitionViewModel
+
+ @Before
+ fun setUp() {
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ biometricSettingsRepository = kosmos.biometricSettingsRepository
+ underTest = kosmos.alternateBouncerToAodTransitionViewModel
+ }
@Test
fun deviceEntryParentViewAppear() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
index 3f7e0df427c6..d4438516a023 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -49,7 +49,9 @@ class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
}
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel
+ private val underTest by lazy {
+ kosmos.alternateBouncerToGoneTransitionViewModel
+ }
@Test
fun deviceEntryParentViewDisappear() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index c7ab52974852..ff41ea25f470 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -47,8 +47,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
}
}
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val underTest = kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel }
@Test
fun deviceEntryParentViewDisappear() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index f1690dafe75a..89e29cf14451 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -20,10 +20,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -41,6 +43,7 @@ import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,9 +53,16 @@ import org.junit.runner.RunWith
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
- private val underTest = kosmos.dreamingToLockscreenTransitionViewModel
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var underTest: DreamingToLockscreenTransitionViewModel
+
+ @Before
+ fun setUp() {
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ underTest = kosmos.dreamingToLockscreenTransitionViewModel
+ }
@Test
fun shortcutsAlpha_bothShortcutsReceiveLastValue() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index f763a6790b51..36b26a490fdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -29,6 +30,7 @@ import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,8 +39,14 @@ import org.junit.runner.RunWith
class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardTransitionRepository
- private val underTest = kosmos.goneToDreamingTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var underTest: GoneToDreamingTransitionViewModel
+
+ @Before
+ fun setUp() {
+ repository = kosmos.fakeKeyguardTransitionRepository
+ underTest = kosmos.goneToDreamingTransitionViewModel
+ }
@Test
fun runTest() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index fd2fd2f04cde..cceb76725615 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -54,11 +55,12 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
private val screenOffAnimationController = kosmos.screenOffAnimationController
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
private val dozeParameters = kosmos.dozeParameters
- private val underTest = kosmos.keyguardRootViewModel
+ private val underTest by lazy { kosmos.keyguardRootViewModel }
@Before
fun setUp() {
@@ -205,6 +207,19 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
}
@Test
+ fun topClippingBounds() =
+ testScope.runTest {
+ val topClippingBounds by collectLastValue(underTest.topClippingBounds)
+ assertThat(topClippingBounds).isNull()
+
+ keyguardRepository.topClippingBounds.value = 50
+ assertThat(topClippingBounds).isEqualTo(50)
+
+ keyguardRepository.topClippingBounds.value = 1000
+ assertThat(topClippingBounds).isEqualTo(1000)
+ }
+
+ @Test
fun alpha_glanceableHubOpen_isZero() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index aa15d0befa80..4595fbfab0d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -47,9 +47,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val underTest = createLockscreenSceneViewModel()
+ private val underTest by lazy {
+ createLockscreenSceneViewModel()
+ }
@Test
fun upTransitionSceneKey_canSwipeToUnlock_gone() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 74025fd6e100..8f04ec3814eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -27,18 +27,22 @@ import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.testKosmos
import com.google.common.collect.Range
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
@@ -51,10 +55,18 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val underTest = kosmos.lockscreenToDreamingTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var shadeRepository: ShadeRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LockscreenToDreamingTransitionViewModel
+
+ @Before
+ fun setUp() {
+ repository = kosmos.fakeKeyguardTransitionRepository
+ shadeRepository = kosmos.shadeRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ underTest = kosmos.lockscreenToDreamingTransitionViewModel
+ }
@Test
fun lockscreenFadeOut() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 6fcb0c11edad..b120f8776d9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -22,12 +22,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -35,12 +38,14 @@ import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.testKosmos
import com.google.common.collect.Range
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
@@ -52,11 +57,20 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardTransitionRepository
- private val shadeRepository = kosmos.shadeRepository
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val configurationRepository = kosmos.fakeConfigurationRepository
- private val underTest = kosmos.lockscreenToOccludedTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var shadeRepository: ShadeRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var underTest: LockscreenToOccludedTransitionViewModel
+
+ @Before
+ fun setUp() {
+ repository = kosmos.fakeKeyguardTransitionRepository
+ shadeRepository = kosmos.shadeRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ configurationRepository = kosmos.fakeConfigurationRepository
+ underTest = kosmos.lockscreenToOccludedTransitionViewModel
+ }
@Test
fun lockscreenFadeOut() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 639114c15000..dddf6485d0f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -49,7 +49,9 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
val configurationRepository = kosmos.fakeConfigurationRepository
- val underTest = kosmos.occludedToLockscreenTransitionViewModel
+ val underTest by lazy {
+ kosmos.occludedToLockscreenTransitionViewModel
+ }
@Test
fun lockscreenFadeIn() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 30b87bbbcf11..30ac34402ffd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -54,7 +54,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
val primaryBouncerInteractor = kosmos.primaryBouncerInteractor
val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController
- val underTest = kosmos.primaryBouncerToGoneTransitionViewModel
+ val underTest by lazy {
+ kosmos.primaryBouncerToGoneTransitionViewModel
+ }
@Before
fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index 96d57743e2ee..4207a9c27ad0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -54,7 +54,9 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+ private val kosmos by lazy {
+ Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+ }
// Getter here so it can change when there is a managed profile.
private val workTileAvailable: Boolean
get() = hasManagedProfile()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index 8ee6d2005350..d05e98faee22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -36,8 +36,9 @@ import org.junit.runner.RunWith
class ColorCorrectionTileMapperTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig
- private val subtitleArray =
+ private val subtitleArray by lazy {
context.resources.getStringArray(R.array.tile_states_color_correction)
+ }
// Using lazy (versus =) to make sure we override the right context -- see b/311612168
private val mapper by lazy {
ColorCorrectionTileMapper(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index a84b9fa32d85..da60c18dcfd7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -308,21 +308,27 @@ class CustomTileRepositoryTest : SysuiTestCase() {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
val TEST_USER_1 = UserHandle.of(1)!!
- val TEST_TILE_1 =
+ val TEST_TILE_1 by lazy {
Tile().apply {
label = "test_tile_1"
icon = Icon.createWithContentUri("file://test_1")
}
+ }
val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier)
- val TEST_DEFAULTS_1 = CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+ val TEST_DEFAULTS_1 by lazy {
+ CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
+ }
val TEST_USER_2 = UserHandle.of(2)!!
- val TEST_TILE_2 =
+ val TEST_TILE_2 by lazy {
Tile().apply {
label = "test_tile_2"
icon = Icon.createWithContentUri("file://test_2")
}
+ }
val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier)
- val TEST_DEFAULTS_2 = CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+ val TEST_DEFAULTS_2 by lazy {
+ CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 20653ca18efc..995d6ac66137 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -180,11 +180,14 @@ class CustomTileInteractorTest : SysuiTestCase() {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
val TEST_USER = UserHandle.of(1)!!
- val TEST_TILE =
+ val TEST_TILE by lazy {
Tile().apply {
label = "test_tile_1"
icon = Icon.createWithContentUri("file://test_1")
}
- val TEST_DEFAULTS = CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+ }
+ val TEST_DEFAULTS by lazy {
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index f3c3579966fd..ccd7ed92b884 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -39,7 +39,9 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
private val colorInversionTileConfig = kosmos.qsColorInversionTileConfig
private val subtitleArrayId =
SubtitleArrayMapping.getSubtitleId(colorInversionTileConfig.tileSpec.spec)
- private val subtitleArray = context.resources.getStringArray(subtitleArrayId)
+ private val subtitleArray by lazy {
+ context.resources.getStringArray(subtitleArrayId)
+ }
// Using lazy (versus =) to make sure we override the right context -- see b/311612168
private val mapper by lazy {
ColorInversionTileMapper(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
index 7497ebdc2978..46e1609b02ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileDataInteractorTest.kt
@@ -53,8 +53,9 @@ import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
class UiModeNightTileDataInteractorTest : SysuiTestCase() {
- private val configurationController: ConfigurationController =
+ private val configurationController: ConfigurationController by lazy {
ConfigurationControllerImpl(context)
+ }
private val batteryController = FakeBatteryController(LeakCheck())
private val locationController = FakeLocationController(LeakCheck())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index be523b813494..d7a794149869 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -52,7 +52,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 1cd764e01bad..1e5ebd0c2acd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -123,30 +123,32 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
private val testScope = kosmos.testScope
- private val sceneContainerConfig = kosmos.sceneContainerConfig
- private val sceneInteractor = kosmos.sceneInteractor
- private val authenticationInteractor = kosmos.authenticationInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
- private val communalInteractor = kosmos.communalInteractor
+ private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+ private val communalInteractor by lazy { kosmos.communalInteractor }
- private val transitionState =
+ private val transitionState by lazy {
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
)
- private val sceneContainerViewModel =
+ }
+ private val sceneContainerViewModel by lazy {
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
)
.apply { setTransitionState(transitionState) }
+ }
- private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
private lateinit var bouncerViewModel: BouncerViewModel
- private val lockscreenSceneViewModel =
+ private val lockscreenSceneViewModel by lazy {
LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
@@ -157,6 +159,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
),
notifications = kosmos.notificationsPlaceholderViewModel,
)
+ }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -179,8 +182,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
- private val keyguardInteractor = kosmos.keyguardInteractor
- private val powerInteractor = kosmos.powerInteractor
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
private var bouncerSceneJob: Job? = null
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index fc0df1288553..16cb6231a872 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -79,13 +79,13 @@ class SceneContainerStartableTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val sceneContainerFlags = kosmos.fakeSceneContainerFlags
- private val authenticationInteractor = kosmos.authenticationInteractor
- private val bouncerInteractor = kosmos.bouncerInteractor
- private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
+ private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
+ private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ede453d85ee1..a16ce4325dcf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -41,7 +41,7 @@ import org.junit.runner.RunWith
class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val interactor = kosmos.sceneInteractor
+ private val interactor by lazy { kosmos.sceneInteractor }
private lateinit var underTest: SceneContainerViewModel
@Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 51745023c5be..e9a2a3befb03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations
class ShadeHeaderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 251daffe2d91..5ef095fd8201 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -60,8 +60,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ef2046d85a14..ad4b98bdb3ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -69,7 +69,9 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() {
private lateinit var controller: CommunalSmartspaceController
// TODO(b/272811280): Remove usage of real view
- private val fakeParent = FrameLayout(context)
+ private val fakeParent by lazy {
+ FrameLayout(context)
+ }
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e09385934991..3a386311223f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -73,8 +73,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var weatherViewComponent: SmartspaceViewComponent
- @Spy
- private var weatherSmartspaceView: SmartspaceView = TestView(context)
+ private val weatherSmartspaceView: SmartspaceView by lazy {
+ Mockito.spy(TestView(context))
+ }
@Mock
private lateinit var targetFilter: SmartspaceTargetFilter
@@ -88,8 +89,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var precondition: SmartspacePrecondition
- @Spy
- private var smartspaceView: SmartspaceView = TestView(context)
+ private val smartspaceView: SmartspaceView by lazy {
+ Mockito.spy(TestView(context))
+ }
@Mock
private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
@@ -100,7 +102,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
private lateinit var controller: DreamSmartspaceController
// TODO(b/272811280): Remove usage of real view
- private val fakeParent = FrameLayout(context)
+ private val fakeParent by lazy {
+ FrameLayout(context)
+ }
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
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 607996d67d4f..6a2e31739c77 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
@@ -56,9 +56,9 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
}
}
private val testScope = kosmos.testScope
- private val placeholderViewModel = kosmos.notificationsPlaceholderViewModel
- private val appearanceViewModel = kosmos.notificationStackAppearanceViewModel
- private val sceneInteractor = kosmos.sceneInteractor
+ private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel }
+ private val appearanceViewModel by lazy { kosmos.notificationStackAppearanceViewModel }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
@Test
fun updateBounds() =
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
new file mode 100644
index 000000000000..045c19eb09dc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
new file mode 100644
index 000000000000..5e012ab7edbd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:fillColor="#fff"/>
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
new file mode 100644
index 000000000000..d8a9a703260d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
+ android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
new file mode 100644
index 000000000000..dec9930959a0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ >
+ <path
+ android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
+ android:fillColor="#fff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
new file mode 100644
index 000000000000..ee4d05c3bda5
--- /dev/null
+++ b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<!-- Base layout that provides a single bindable icon_view id image view -->
+<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ >
+
+ <ImageView
+ android:id="@+id/icon_view"
+ android:layout_height="@dimen/status_bar_bindable_icon_size"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:padding="@dimen/status_bar_bindable_icon_padding"
+ android:scaleType="fitCenter"
+ />
+
+</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView>
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 3be99939ba0f..156c98333f3a 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -37,6 +37,7 @@
android:layout_height="wrap_content"
android:background="@drawable/qs_customizer_toolbar"
android:navigationContentDescription="@*android:string/action_bar_up_description"
+ android:titleTextAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
style="@style/QSCustomizeToolbar"
/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ee2a1ceab2b7..c630a7ff09b6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -151,6 +151,8 @@
<dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
<!-- Original dp height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
+ <dimen name="status_bar_bindable_icon_size">20sp</dimen>
+ <dimen name="status_bar_bindable_icon_padding">2sp</dimen>
<!-- Default horizontal drawable padding for status bar icons. -->
<dimen name="status_bar_horizontal_padding">2.5sp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 033f93b260ab..ad303171d22e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -477,9 +477,13 @@ public class KeyguardClockSwitch extends RelativeLayout {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
pw.println(" mSmallClockFrame = " + mSmallClockFrame);
- pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+ if (mSmallClockFrame != null) {
+ pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+ }
pw.println(" mLargeClockFrame = " + mLargeClockFrame);
- pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+ if (mLargeClockFrame != null) {
+ pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+ }
pw.println(" mStatusArea = " + mStatusArea);
pw.println(" mDisplayedClockSize = " + mDisplayedClockSize);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6869bbaedcf3..97999cc19dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -240,7 +240,9 @@ class MenuViewLayer extends FrameLayout implements
mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
if (Flags.floatingMenuDragToHide()) {
dismissNotification();
- undo();
+ if (newTargetFeatures.size() > 0) {
+ undo();
+ }
} else {
if (newTargetFeatures.size() < 1) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 92d01dbe3c3e..44b0383e12c6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
+import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,6 +63,7 @@ constructor(
private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
+ userRepository: UserRepository,
keyguardInteractor: KeyguardInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
@@ -75,7 +77,14 @@ constructor(
val isCommunalAvailable: StateFlow<Boolean> =
flowOf(isCommunalEnabled)
.flatMapLatest { enabled ->
- if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+ if (enabled)
+ combine(
+ keyguardInteractor.isEncryptedOrLockdown,
+ userRepository.selectedUserInfo,
+ ) { isEncryptedOrLockdown, selectedUserInfo ->
+ !isEncryptedOrLockdown && selectedUserInfo.isMain
+ }
+ else flowOf(false)
}
.distinctUntilChanged()
.onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
@@ -263,6 +272,12 @@ constructor(
companion object {
/**
+ * The user activity timeout which should be used when the communal hub is opened. A value
+ * of -1 means that the user's chosen screen timeout will be used instead.
+ */
+ const val AWAKE_INTERVAL_MS = -1
+
+ /**
* Calculates the content size dynamically based on the total number of contents of that
* type.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 5ca89f28f1fc..4e5be9b8aa5e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -45,14 +45,17 @@ constructor(
private val communalTutorialRepository: CommunalTutorialRepository,
keyguardInteractor: KeyguardInteractor,
private val communalRepository: CommunalRepository,
+ communalInteractor: CommunalInteractor,
) {
/** An observable for whether the tutorial is available. */
val isTutorialAvailable: Flow<Boolean> =
combine(
+ communalInteractor.isCommunalAvailable,
keyguardInteractor.isKeyguardVisible,
communalTutorialRepository.tutorialSettingState,
- ) { isKeyguardVisible, tutorialSettingState ->
- isKeyguardVisible &&
+ ) { isCommunalAvailable, isKeyguardVisible, tutorialSettingState ->
+ isCommunalAvailable &&
+ isKeyguardVisible &&
tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2bb9d0e52757..a909383f4a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -74,6 +74,7 @@ constructor(
// before the MediaHierarchyManager attempts to move the UMO to the hub.
with(mediaHost) {
expansion = MediaHostState.EXPANDED
+ expandedMatchesParentHeight = true
showsOnlyActiveMedia = false
falsingProtectionNeeded = false
init(MediaHierarchyManager.LOCATION_COMMUNAL_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d012d24b767e..14371949c9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -132,6 +132,9 @@ interface KeyguardRepository {
*/
val isDozing: StateFlow<Boolean>
+ /** Keyguard can be clipped at the top as the shade is dragged */
+ val topClippingBounds: MutableStateFlow<Int?>
+
/**
* Observable for whether the device is dreaming.
*
@@ -326,6 +329,8 @@ constructor(
private val _clockShouldBeCentered = MutableStateFlow(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
+ override val topClippingBounds = MutableStateFlow<Int?>(null)
+
override val isKeyguardShowing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6170356d4a90..91747e0f69a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -171,6 +171,14 @@ constructor(
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+ /** Keyguard can be clipped at the top as the shade is dragged */
+ val topClippingBounds: Flow<Int?> =
+ combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
+ _,
+ topClippingBounds ->
+ topClippingBounds
+ }
+
/** Last point that [KeyguardRootView] view was tapped */
val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
@@ -328,6 +336,10 @@ constructor(
repository.keyguardDoneAnimationsFinished()
}
+ fun setTopClippingBounds(top: Int?) {
+ repository.topClippingBounds.value = top
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
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 2aebd99e3664..48092c6374e0 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
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
import android.annotation.DrawableRes
import android.annotation.SuppressLint
import android.graphics.Point
+import android.graphics.Rect
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -158,6 +159,23 @@ object KeyguardRootViewBinder {
}
launch {
+ val clipBounds = Rect()
+ viewModel.topClippingBounds.collect { clipTop ->
+ if (clipTop == null) {
+ view.setClipBounds(null)
+ } else {
+ clipBounds.apply {
+ top = clipTop
+ left = view.getLeft()
+ right = view.getRight()
+ bottom = view.getBottom()
+ }
+ view.setClipBounds(clipBounds)
+ }
+ }
+ }
+
+ launch {
viewModel.lockscreenStateAlpha.collect { alpha ->
childViews[statusViewId]?.alpha = alpha
}
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 5d36da9b9df1..ea6670267270 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
@@ -80,6 +80,12 @@ constructor(
val notificationBounds: StateFlow<NotificationContainerBounds> =
keyguardInteractor.notificationContainerBounds
+ /**
+ * The keyguard root view can be clipped as the shade is pulled down, typically only for
+ * non-split shade cases.
+ */
+ val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
+
/** An observable for the alpha level for the entire keyguard root view. */
val alpha: Flow<Float> =
merge(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index 631a0b8471b8..437218f9f440 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -228,6 +228,14 @@ constructor(
}
}
+ override var expandedMatchesParentHeight: Boolean = false
+ set(value) {
+ if (value != field) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
override var squishFraction: Float = 1.0f
set(value) {
if (!value.equals(field)) {
@@ -282,6 +290,7 @@ constructor(
override fun copy(): MediaHostState {
val mediaHostState = MediaHostStateHolder()
mediaHostState.expansion = expansion
+ mediaHostState.expandedMatchesParentHeight = expandedMatchesParentHeight
mediaHostState.squishFraction = squishFraction
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
mediaHostState.measurementInput = measurementInput?.copy()
@@ -360,6 +369,12 @@ interface MediaHostState {
*/
var expansion: Float
+ /**
+ * If true, the [EXPANDED] layout should stretch to match the height of its parent container,
+ * rather than having a fixed height.
+ */
+ var expandedMatchesParentHeight: Boolean
+
/** Fraction of the height animation. */
var squishFraction: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index be9393655c5d..962764c028fc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.Configuration
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import com.android.app.tracing.traceSection
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -152,18 +153,11 @@ constructor(
lastOrientation = newOrientation
// Update the height of media controls for the expanded layout. it is needed
// for large screen devices.
- val backgroundIds =
- if (type == TYPE.PLAYER) {
- MediaViewHolder.backgroundIds
- } else {
- setOf(RecommendationViewHolder.backgroundId)
- }
- backgroundIds.forEach { id ->
- expandedLayout.getConstraint(id).layout.mHeight =
- context.resources.getDimensionPixelSize(
- R.dimen.qs_media_session_height_expanded
- )
- }
+ setBackgroundHeights(
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ )
}
if (this@MediaViewController::configurationChangeListener.isInitialized) {
configurationChangeListener.invoke()
@@ -276,6 +270,17 @@ constructor(
private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
if (expansion > 0) expandedLayout else collapsedLayout
+ /** Set the height of UMO background constraints. */
+ private fun setBackgroundHeights(height: Int) {
+ val backgroundIds =
+ if (type == TYPE.PLAYER) {
+ MediaViewHolder.backgroundIds
+ } else {
+ setOf(RecommendationViewHolder.backgroundId)
+ }
+ backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height }
+ }
+
/**
* Set the views to be showing/hidden based on the [isGutsVisible] for a given
* [TransitionViewState].
@@ -454,6 +459,18 @@ constructor(
}
// Let's create a new measurement
if (state.expansion == 0.0f || state.expansion == 1.0f) {
+ if (state.expansion == 1.0f) {
+ val height =
+ if (state.expandedMatchesParentHeight) {
+ MATCH_CONSTRAINT
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ setBackgroundHeights(height)
+ }
+
result =
transitionLayout!!.calculateViewState(
state.measurementInput!!,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index a103566400a6..07705f361881 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -58,6 +58,7 @@ public class QSCustomizer extends LinearLayout {
private final RecyclerView mRecyclerView;
private boolean mCustomizing;
private QSContainerController mQsContainerController;
+ private final Toolbar mToolbar;
private QS mQs;
private int mX;
private int mY;
@@ -69,15 +70,15 @@ public class QSCustomizer extends LinearLayout {
LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
- Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
+ mToolbar = findViewById(com.android.internal.R.id.action_bar);
TypedValue value = new TypedValue();
mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
- toolbar.setNavigationIcon(
+ mToolbar.setNavigationIcon(
getResources().getDrawable(value.resourceId, mContext.getTheme()));
- toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+ mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- toolbar.setTitle(R.string.qs_edit);
+ mToolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
DefaultItemAnimator animator = new DefaultItemAnimator();
@@ -184,6 +185,14 @@ public class QSCustomizer extends LinearLayout {
return isShown;
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mToolbar.setTitleTextAppearance(mContext,
+ android.R.style.TextAppearance_DeviceDefault_Widget_ActionBar_Title);
+ updateToolbarMenuFontSize();
+ }
+
void setCustomizing(boolean customizing) {
mCustomizing = customizing;
if (mQs != null) {
@@ -269,4 +278,11 @@ public class QSCustomizer extends LinearLayout {
lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
mTransparentView.setLayoutParams(lp);
}
+
+ private void updateToolbarMenuFontSize() {
+ // Clearing and re-adding the toolbar action force updates the font size
+ mToolbar.getMenu().clear();
+ mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
} \ No newline at end of file
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 456520051f58..c5eeb2f87d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -249,6 +249,10 @@ open class QSTileViewImpl @JvmOverloads constructor(
height = iconSize
marginEnd = endMargin
}
+
+ background = createTileBackground()
+ setColor(backgroundColor)
+ setOverlayColor(backgroundOverlayColor)
}
private fun createAndAddLabels() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 60feb82bf4aa..00534743588c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -118,6 +119,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final SceneContainerFlags mSceneContainerFlags;
+ private final Lazy<CommunalInteractor> mCommunalInteractor;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
private boolean mHasTopUi;
@@ -165,7 +167,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
mWindowManager = windowManager;
@@ -184,6 +187,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mAuthController = authController;
mUserInteractor = userInteractor;
mSceneContainerFlags = sceneContainerFlags;
+ mCommunalInteractor = communalInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -325,6 +329,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mShadeInteractorLazy.get().isQsExpanded(),
this::onQsExpansionChanged
);
+ collectFlow(
+ mWindowRootView,
+ mCommunalInteractor.get().isCommunalShowing(),
+ this::onCommunalShowingChanged
+ );
}
@Override
@@ -501,14 +510,21 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private void applyUserActivityTimeout(NotificationShadeWindowState state) {
- if (state.isKeyguardShowingAndNotOccluded()
+ final Boolean communalShowing = state.isCommunalShowingAndNotOccluded();
+ final Boolean keyguardShowing = state.isKeyguardShowingAndNotOccluded();
+ long timeout = -1;
+ if ((communalShowing || keyguardShowing)
&& state.statusBarState == StatusBarState.KEYGUARD
&& !state.qsExpanded) {
- mLpChanged.userActivityTimeout = state.bouncerShowing
- ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
- } else {
- mLpChanged.userActivityTimeout = -1;
+ if (state.bouncerShowing) {
+ timeout = KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS;
+ } else if (communalShowing) {
+ timeout = CommunalInteractor.AWAKE_INTERVAL_MS;
+ } else if (keyguardShowing) {
+ timeout = mLockScreenDisplayTimeout;
+ }
}
+ mLpChanged.userActivityTimeout = timeout;
}
private void applyInputFeatures(NotificationShadeWindowState state) {
@@ -607,7 +623,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
state.forcePluginOpen,
state.dozing,
state.scrimsVisibility,
- state.backgroundBlurRadius
+ state.backgroundBlurRadius,
+ state.communalShowing
);
}
@@ -731,6 +748,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
apply(mCurrentState);
}
+ @VisibleForTesting
+ void onCommunalShowingChanged(Boolean showing) {
+ mCurrentState.communalShowing = showing;
+ apply(mCurrentState);
+ }
+
@Override
public void setForceUserActivity(boolean forceUserActivity) {
mCurrentState.forceUserActivity = forceUserActivity;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 0b20170834d8..f9c9d83e03aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -58,12 +58,17 @@ class NotificationShadeWindowState(
@JvmField var dreaming: Boolean = false,
@JvmField var scrimsVisibility: Int = 0,
@JvmField var backgroundBlurRadius: Int = 0,
+ @JvmField var communalShowing: Boolean = false,
) {
fun isKeyguardShowingAndNotOccluded(): Boolean {
return keyguardShowing && !keyguardOccluded
}
+ fun isCommunalShowingAndNotOccluded(): Boolean {
+ return communalShowing && !keyguardOccluded
+ }
+
/** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
val asStringList: List<String> by lazy {
listOf(
@@ -93,7 +98,8 @@ class NotificationShadeWindowState(
forcePluginOpen.toString(),
dozing.toString(),
scrimsVisibility.toString(),
- backgroundBlurRadius.toString()
+ backgroundBlurRadius.toString(),
+ communalShowing.toString(),
)
}
@@ -134,6 +140,7 @@ class NotificationShadeWindowState(
dozing: Boolean,
scrimsVisibility: Int,
backgroundBlurRadius: Int,
+ communalShowing: Boolean,
) {
buffer.advance().apply {
this.keyguardShowing = keyguardShowing
@@ -165,6 +172,7 @@ class NotificationShadeWindowState(
this.dozing = dozing
this.scrimsVisibility = scrimsVisibility
this.backgroundBlurRadius = backgroundBlurRadius
+ this.communalShowing = communalShowing
}
}
@@ -209,7 +217,8 @@ class NotificationShadeWindowState(
"forcePluginOpen",
"dozing",
"scrimsVisibility",
- "backgroundBlurRadius"
+ "backgroundBlurRadius",
+ "communalShowing"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 46806e66cb8c..54b6ad71e734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -790,7 +790,7 @@ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R {
/** Mutates the HeadsUp state of notifications. */
private interface HunMutator {
- fun updateNotification(key: String, alert: Boolean)
+ fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
fun removeNotification(key: String, releaseImmediately: Boolean)
}
@@ -801,8 +801,8 @@ private interface HunMutator {
private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator {
private val deferred = mutableListOf<Pair<String, Boolean>>()
- override fun updateNotification(key: String, alert: Boolean) {
- headsUpManager.updateNotification(key, alert)
+ override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) {
+ headsUpManager.updateNotification(key, shouldHeadsUpAgain)
}
override fun removeNotification(key: String, releaseImmediately: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 380cdadd1361..ae4ba27775b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.screenshareNotificationHiding
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -30,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
@@ -55,6 +57,8 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
private val statusBarStateController: StatusBarStateController,
private val keyguardStateController: KeyguardStateController,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val sensitiveNotificationProtectionController:
+ SensitiveNotificationProtectionController,
) : Invalidator("SensitiveContentInvalidator"),
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
@@ -82,10 +86,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
return
}
+ val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive
val currentUserId = lockscreenUserManager.currentUserId
val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
- val deviceSensitive = devicePublic &&
- !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+ val deviceSensitive = (devicePublic &&
+ !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
+ isSensitiveContentProtectionActive
val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
val notifUserId = entry.sbn.user.identifier
@@ -105,9 +112,13 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
}
}
+
+ val shouldProtectNotification = screenshareNotificationHiding() &&
+ sensitiveNotificationProtectionController.shouldProtectNotification(entry)
+
val needsRedaction = lockscreenUserManager.needsRedaction(entry)
val isSensitive = userPublic && needsRedaction
- entry.setSensitive(isSensitive, deviceSensitive)
+ entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 0bc8e682c891..f375ebce2de3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -187,7 +187,7 @@ object NotificationIconContainerViewBinder {
configuration.getDimensionPixelSize(RInternal.dimen.status_bar_icon_size_sp)
val iconHorizontalPaddingFlow: Flow<Int> =
configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
- val layoutParams: Flow<FrameLayout.LayoutParams> =
+ val layoutParams: StateFlow<FrameLayout.LayoutParams> =
combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) {
iconSize,
iconHPadding,
@@ -206,7 +206,7 @@ object NotificationIconContainerViewBinder {
private suspend fun Flow<NotificationIconsViewData>.bindIcons(
view: NotificationIconContainer,
- layoutParams: Flow<FrameLayout.LayoutParams>,
+ layoutParams: StateFlow<FrameLayout.LayoutParams>,
notifyBindingFailures: (Collection<String>) -> Unit,
viewStore: IconViewStore,
bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit,
@@ -259,7 +259,7 @@ object NotificationIconContainerViewBinder {
// added again.
removeTransientView(sbiv)
}
- view.addView(sbiv)
+ view.addView(sbiv, layoutParams.value)
boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
boundViewsByNotifKey[notifKey] =
Pair(
@@ -267,7 +267,9 @@ object NotificationIconContainerViewBinder {
launch {
launch {
layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
- sbiv.layoutParams = it
+ if (it != sbiv.layoutParams) {
+ sbiv.layoutParams = it
+ }
}
}
bindIcon(notifKey, sbiv)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
new file mode 100644
index 000000000000..a21dd9bb9579
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
@@ -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 com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAvalancheSuppression {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAvalancheSuppression()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+} \ No newline at end of file
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 6a66bb74f16d..9a8cc0ae33cb 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
@@ -22,6 +22,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.app.animation.Interpolators.STANDARD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -135,6 +136,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -218,6 +220,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
private final ActivityStarter mActivityStarter;
+ private final SensitiveNotificationProtectionController
+ mSensitiveNotificationProtectionController;
private View mLongPressedView;
@@ -295,6 +299,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
};
+ private final Runnable mSensitiveStateChangedListener = new Runnable() {
+ @Override
+ public void run() {
+ // Animate false to protect against screen recording capturing content
+ // during the animation
+ updateSensitivenessWithAnimation(false);
+ }
+ };
+
private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
if (mView.isExpanded()) {
// The bottom might change because we're using the final actual height of the view
@@ -399,7 +412,20 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
private void updateSensitivenessWithAnimation(boolean animate) {
- mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ Trace.beginSection("NSSLC.updateSensitivenessWithAnimation");
+ if (screenshareNotificationHiding()) {
+ boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode();
+ boolean isSensitiveContentProtectionActive =
+ mSensitiveNotificationProtectionController.isSensitiveStateActive();
+ boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive;
+
+ // Only animate if in a non-sensitive state (not screen sharing)
+ boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
+ mView.updateSensitiveness(shouldAnimate, isSensitive);
+ } else {
+ mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+ }
+ Trace.endSection();
}
/**
@@ -708,7 +734,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
ActivityStarter activityStarter,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
mView = view;
mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
@@ -756,6 +783,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
mActivityStarter = activityStarter;
+ mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
mView.passSplitShadeStateController(splitShadeStateController);
mDumpManager.registerDumpable(this);
updateResources();
@@ -860,6 +888,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mDeviceProvisionedListener.onDeviceProvisionedChanged();
+ if (screenshareNotificationHiding()) {
+ mSensitiveNotificationProtectionController
+ .registerSensitiveStateListener(mSensitiveStateChangedListener);
+ }
+
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index ae04eaf49b65..459b368b5ac9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -217,6 +218,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardInteractor mKeyguardInteractor;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -311,6 +313,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
+ KeyguardInteractor keyguardInteractor,
WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
@@ -357,6 +360,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
}
@@ -759,7 +763,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
// see: b/186644628
mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
mScrimBehind.setBottomEdgePosition((int) top);
+ mKeyguardInteractor.setTopClippingBounds((int) top);
} else {
+ mKeyguardInteractor.setTopClippingBounds(null);
mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
new file mode 100644
index 000000000000..252945f1ed6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import javax.inject.Qualifier
+
+/** Detailed [DeviceBasedSatelliteRepository] logs */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class OemSatelliteInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index e309c32df64e..2b90e649a154 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -265,6 +265,13 @@ abstract class StatusBarPipelineModule {
return factory.create("VerboseMobileViewLog", 100)
}
+ @Provides
+ @SysUISingleton
+ @OemSatelliteInputLog
+ fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("DeviceBasedSatelliteInputLog", 32)
+ }
+
const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
"FirstMobileSubShowingNetworkTypeIcon"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
index e3c3139f6906..8400fb08e147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.icons.shared
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon
import javax.inject.Inject
/**
@@ -38,11 +39,12 @@ interface BindableIconsRegistry {
class BindableIconsRegistryImpl
@Inject
constructor(
-/** Bindables go here */
+ /** Bindables go here */
+ oemSatellite: DeviceBasedSatelliteBindableIcon
) : BindableIconsRegistry {
/**
* Adding the injected bindables to this list will get them registered with
* StatusBarIconController
*/
- override val bindableIcons: List<BindableIcon> = listOf()
+ override val bindableIcons: List<BindableIcon> = listOf(oemSatellite)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index bc38b538f8c0..a608be39f7f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -72,6 +72,9 @@ interface MobileConnectionRepository {
*/
val isInService: StateFlow<Boolean>
+ /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */
+ val isNonTerrestrial: StateFlow<Boolean>
+
/** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
val isGsm: StateFlow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index b2a773375cd5..6de7a00b5a10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullM
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_NTN
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
@@ -109,6 +110,17 @@ class DemoMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
+ private val _isNonTerrestrial = MutableStateFlow(false)
+ override val isNonTerrestrial =
+ _isNonTerrestrial
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_NTN,
+ _isNonTerrestrial.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isNonTerrestrial.value)
+
private val _isGsm = MutableStateFlow(false)
override val isGsm =
_isGsm
@@ -227,6 +239,7 @@ class DemoMobileConnectionRepository(
(event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
_carrierNetworkChangeActive.value = event.carrierNetworkChange
_resolvedNetworkType.value = resolvedNetworkType
+ _isNonTerrestrial.value = event.ntn
isAllowedDuringAirplaneMode.value = false
hasPrioritizedNetworkCapabilities.value = event.slice
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index 4cd877eb1a14..11a61a9fd319 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -77,6 +77,7 @@ constructor(
val roaming = getString("roam") == "show"
val name = getString("networkname") ?: "demo mode"
val slice = getString("slice").toBoolean()
+ val ntn = getString("ntn").toBoolean()
return Mobile(
level = level,
@@ -89,6 +90,7 @@ constructor(
roaming = roaming,
name = name,
slice = slice,
+ ntn = ntn,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 0aa95f8821cc..4836abe73f86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -37,6 +37,7 @@ sealed interface FakeNetworkEventModel {
val roaming: Boolean,
val name: String,
val slice: Boolean = false,
+ val ntn: Boolean = false,
) : FakeNetworkEventModel
data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index e5a5695d3655..f8858c5037ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -168,6 +168,7 @@ class CarrierMergedConnectionRepository(
override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
override val isInService = MutableStateFlow(true).asStateFlow()
+ override val isNonTerrestrial = MutableStateFlow(false).asStateFlow()
override val isGsm = MutableStateFlow(false).asStateFlow()
override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 48bf7ac60ba7..a1241965de7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -175,6 +175,21 @@ class FullMobileConnectionRepository(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
+ override val isNonTerrestrial =
+ activeRepo
+ .flatMapLatest { it.isNonTerrestrial }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_NTN,
+ activeRepo.value.isNonTerrestrial.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.isNonTerrestrial.value
+ )
+
override val isGsm =
activeRepo
.flatMapLatest { it.isGsm }
@@ -366,6 +381,7 @@ class FullMobileConnectionRepository(
const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
const val COL_CDMA_LEVEL = "cdmaLevel"
const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_IS_NTN = "isNtn"
const val COL_IS_GSM = "isGsm"
const val COL_IS_IN_SERVICE = "isInService"
const val COL_OPERATOR = "operatorName"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index f44401ba4702..77fd6bef8a33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -227,6 +227,12 @@ class MobileConnectionRepositoryImpl(
.map { Utils.isInService(it.serviceState) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isNonTerrestrial =
+ callbackEvents
+ .mapNotNull { it.onServiceStateChanged }
+ .map { it.serviceState.isUsingNonTerrestrialNetwork }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
override val isGsm =
callbackEvents
.mapNotNull { it.onSignalStrengthChanged }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index de46a5ed99d6..0e6775685611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -19,11 +19,18 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod
import android.os.OutcomeReceiver
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
import android.telephony.satellite.SatelliteModemStateCallback
+import androidx.annotation.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
@@ -62,8 +69,10 @@ private typealias SupportedSatelliteManager = SatelliteManager
/**
* "Supported" here means supported by the device. The value of this should be stable during the
* process lifetime.
+ *
+ * @VisibleForTesting
*/
-private sealed interface SatelliteSupport {
+sealed interface SatelliteSupport {
/** Not yet fetched */
data object Unknown : SatelliteSupport
@@ -123,6 +132,7 @@ constructor(
satelliteManagerOpt: Optional<SatelliteManager>,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
+ @OemSatelliteInputLog private val logBuffer: LogBuffer,
private val systemClock: SystemClock,
) : DeviceBasedSatelliteRepository {
@@ -132,7 +142,8 @@ constructor(
// Some calls into satellite manager will throw exceptions if it is not supported.
// This is never expected to change after boot, but may need to be retried in some cases
- private val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
+ @get:VisibleForTesting
+ val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
init {
satelliteManager = satelliteManagerOpt.getOrNull()
@@ -145,6 +156,11 @@ constructor(
ensureMinUptime(systemClock, MIN_UPTIME)
satelliteSupport.value = satelliteManager.checkSatelliteSupported()
+ logBuffer.i(
+ { str1 = satelliteSupport.value.toString() },
+ { "Checked for system support. support=$str1" },
+ )
+
// We only need to check location availability if this mode is supported
if (satelliteSupport.value is Supported) {
isSatelliteAllowedForCurrentLocation.subscriptionCount
@@ -159,6 +175,9 @@ constructor(
* connection might cause more frequent checks.
*/
while (true) {
+ logBuffer.i {
+ "requestIsSatelliteCommunicationAllowedForCurrentLocation"
+ }
checkIsSatelliteAllowed()
delay(POLLING_INTERVAL_MS)
}
@@ -167,6 +186,8 @@ constructor(
}
}
} else {
+ logBuffer.i { "Satellite manager is null" }
+
satelliteSupport.value = NotSupported
}
}
@@ -181,12 +202,21 @@ constructor(
private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
conflatedCallbackFlow {
val cb = SatelliteModemStateCallback { state ->
+ logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" }
trySend(SatelliteConnectionState.fromModemState(state))
}
- sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
+ var registered = false
- awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) }
+ try {
+ val res =
+ sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
+ registered = res == SATELLITE_RESULT_SUCCESS
+ } catch (e: Exception) {
+ logBuffer.e("error registering for modem state", e)
+ }
+
+ awaitClose { if (registered) sm.unregisterForSatelliteModemStateChanged(cb) }
}
.flowOn(bgDispatcher)
@@ -197,12 +227,21 @@ constructor(
private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
conflatedCallbackFlow {
val cb = NtnSignalStrengthCallback { signalStrength ->
+ logBuffer.i({ int1 = signalStrength.level }) {
+ "onNtnSignalStrengthChanged: level=$int1"
+ }
trySend(signalStrength.level)
}
- sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
+ var registered = false
+ try {
+ sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
+ registered = true
+ } catch (e: Exception) {
+ logBuffer.e("error registering for signal strength", e)
+ }
- awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) }
+ awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) }
}
.flowOn(bgDispatcher)
@@ -213,11 +252,15 @@ constructor(
bgDispatcher.asExecutor(),
object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
override fun onError(e: SatelliteManager.SatelliteException) {
- android.util.Log.e(TAG, "Found exception when checking for satellite: ", e)
+ logBuffer.e(
+ "Found exception when checking availability",
+ e,
+ )
isSatelliteAllowedForCurrentLocation.value = false
}
override fun onResult(allowed: Boolean) {
+ logBuffer.i { allowed.toString() }
isSatelliteAllowedForCurrentLocation.value = allowed
}
}
@@ -239,12 +282,27 @@ constructor(
}
override fun onError(error: SatelliteManager.SatelliteException) {
+ logBuffer.e(
+ "Exception when checking for satellite support. " +
+ "Assuming it is not supported for this device.",
+ error,
+ )
+
// Assume that an error means it's not supported
continuation.resume(NotSupported)
}
}
- requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb)
+ try {
+ requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb)
+ } catch (error: Exception) {
+ logBuffer.e(
+ "Exception when checking for satellite support. " +
+ "Assuming it is not supported for this device.",
+ error,
+ )
+ continuation.resume(NotSupported)
+ }
}
companion object {
@@ -264,5 +322,19 @@ constructor(
delay(timeTilMinUptime)
}
}
+
+ /** A couple of convenience logging methods rather than a whole class */
+ private fun LogBuffer.i(
+ initializer: MessageInitializer = {},
+ printer: MessagePrinter,
+ ) = this.log(TAG, LogLevel.INFO, initializer, printer)
+
+ private fun LogBuffer.e(message: String, exception: Throwable? = null) =
+ this.log(
+ tag = TAG,
+ level = LogLevel.ERROR,
+ message = message,
+ exception = exception,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
new file mode 100644
index 000000000000..f5d0f6b8f07c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui
+
+import android.content.Context
+import com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
+import com.android.systemui.statusbar.pipeline.satellite.ui.binder.DeviceBasedSatelliteIconBinder
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+import javax.inject.Inject
+
+@SysUISingleton
+class DeviceBasedSatelliteBindableIcon
+@Inject
+constructor(
+ context: Context,
+ viewModel: DeviceBasedSatelliteViewModel,
+) : BindableIcon {
+ override val slot: String =
+ context.getString(com.android.internal.R.string.status_bar_oem_satellite)
+
+ override val initializer = ModernStatusBarViewCreator { context ->
+ SingleBindableStatusBarIconView.createView(context).also { view ->
+ view.initView(slot) { DeviceBasedSatelliteIconBinder.bind(view, viewModel) }
+ }
+ }
+
+ override val shouldBindIcon: Boolean = oemEnabledSatelliteFlag()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
new file mode 100644
index 000000000000..59ac5f29b66c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
+import kotlinx.coroutines.launch
+
+object DeviceBasedSatelliteIconBinder {
+ fun bind(
+ view: SingleBindableStatusBarIconView,
+ viewModel: DeviceBasedSatelliteViewModel,
+ ): ModernStatusBarViewBinding {
+ return SingleBindableStatusBarIconView.withDefaultBinding(
+ view = view,
+ shouldBeVisible = { viewModel.icon.value != null }
+ ) {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.icon.collect { newIcon ->
+ if (newIcon == null) {
+ view.iconView.setImageDrawable(null)
+ } else {
+ IconViewBinder.bind(newIcon, view.iconView)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
new file mode 100644
index 000000000000..6938d667ca81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.model
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+
+/**
+ * Define the [Icon] that relates to a given satellite connection state + level. Note that for now
+ * We don't need any data class box, so we can just use a simple mapping function.
+ */
+object SatelliteIconModel {
+ fun fromConnectionState(
+ connectionState: SatelliteConnectionState,
+ signalStrength: Int,
+ ): Icon? =
+ when (connectionState) {
+ // TODO(b/316635648): check if this should be null
+ SatelliteConnectionState.Unknown,
+ SatelliteConnectionState.Off,
+ SatelliteConnectionState.On ->
+ Icon.Resource(
+ res = R.drawable.ic_satellite_not_connected,
+ contentDescription = null,
+ )
+ SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
+ }
+
+ private fun fromSignalStrength(
+ signalStrength: Int,
+ ): Icon? =
+ // TODO(b/316634365): these need content descriptions
+ when (signalStrength) {
+ // No signal
+ 0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
+
+ // Poor -> Moderate
+ 1,
+ 2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
+
+ // Good -> Great
+ 3,
+ 4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
+ else -> null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
new file mode 100644
index 000000000000..0051161eff35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
+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.stateIn
+
+/**
+ * View-Model for the device-based satellite icon. This icon will only show in the status bar if
+ * satellite is available AND all other service states are considered OOS.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceBasedSatelliteViewModel
+@Inject
+constructor(
+ interactor: DeviceBasedSatelliteInteractor,
+ @Application scope: CoroutineScope,
+) {
+ private val shouldShowIcon: StateFlow<Boolean> =
+ interactor.areAllConnectionsOutOfService
+ .flatMapLatest { allOos ->
+ if (!allOos) {
+ flowOf(false)
+ } else {
+ interactor.isSatelliteAllowed
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ val icon: StateFlow<Icon?> =
+ combine(
+ shouldShowIcon,
+ interactor.connectionState,
+ interactor.signalStrength,
+ ) { shouldShow, state, signalStrength ->
+ if (shouldShow) {
+ SatelliteIconModel.fromConnectionState(state, signalStrength)
+ } else {
+ null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 3b87bed2e0ef..25a2c9dd3caf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -103,7 +103,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
*
* Creates a dot view, and uses [bindingCreator] to get and set the binding.
*/
- fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ open fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
// The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
// view. So, this is the required order.
this.slot = slot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
new file mode 100644
index 000000000000..c663c37fec98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/** Simple single-icon view that is bound to bindable_status_bar_icon.xml */
+class SingleBindableStatusBarIconView(
+ context: Context,
+ attrs: AttributeSet?,
+) : ModernStatusBarView(context, attrs) {
+
+ internal lateinit var iconView: ImageView
+ internal lateinit var dotView: StatusBarIconView
+
+ override fun toString(): String {
+ return "SingleBindableStatusBarIcon(" +
+ "slot='$slot', " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
+ override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ super.initView(slot, bindingCreator)
+
+ iconView = requireViewById(R.id.icon_view)
+ dotView = requireViewById(R.id.status_bar_dot)
+ }
+
+ companion object {
+ fun createView(
+ context: Context,
+ ): SingleBindableStatusBarIconView {
+ return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null)
+ as SingleBindableStatusBarIconView
+ }
+
+ /**
+ * Using a given binding [block], create the necessary scaffolding to handle the general
+ * case of a single status bar icon. This includes eliding into a dot view when there is not
+ * enough space, and handling tint.
+ *
+ * [block] should be a simple [launch] call that handles updating the single icon view with
+ * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
+ * for dual-layered icons, and any more complex logic should probably find a way to return
+ * its own version of [ModernStatusBarViewBinding].
+ */
+ fun withDefaultBinding(
+ view: SingleBindableStatusBarIconView,
+ shouldBeVisible: () -> Boolean,
+ block: suspend LifecycleOwner.(View) -> Unit
+ ): SingleBindableStatusBarIconViewBinding {
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
+ val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+ val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
+
+ var isCollecting: Boolean = false
+
+ view.repeatWhenAttached {
+ // Child binding
+ block(view)
+
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // isVisible controls the visibility state of the outer group, and thus it
+ // needs
+ // to run in the CREATED lifecycle so it can continue to watch while
+ // invisible
+ // See (b/291031862) for details
+ launch {
+ visibilityState.collect { visibilityState ->
+ // for b/296864006, we can not hide all the child views if
+ // visibilityState is STATE_HIDDEN. Because hiding all child views
+ // would cause the
+ // getWidth() of this view return 0, and that would cause the
+ // translation
+ // calculation fails in StatusIconContainer. Therefore, like class
+ // MobileIconBinder, instead of set the child views visibility to
+ // View.GONE,
+ // we set their visibility to View.INVISIBLE to make them invisible
+ // but
+ // keep the width.
+ ModernStatusBarViewVisibilityHelper.setVisibilityState(
+ visibilityState,
+ view.iconView,
+ view.dotView,
+ )
+ }
+ }
+
+ launch {
+ iconTint.collect { tint ->
+ val tintList = ColorStateList.valueOf(tint)
+ view.iconView.imageTintList = tintList
+ view.dotView.setDecorColor(tint)
+ }
+ }
+
+ launch {
+ decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
+ }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ }
+ }
+ }
+ }
+
+ return object : SingleBindableStatusBarIconViewBinding {
+ override val decorTint: Int
+ get() = decorTint.value
+
+ override val iconTint: Int
+ get() = iconTint.value
+
+ override fun getShouldIconBeVisible(): Boolean {
+ return shouldBeVisible()
+ }
+
+ override fun onVisibilityStateChanged(state: Int) {
+ visibilityState.value = state
+ }
+
+ override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
+ iconTint.value = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ decorTint.value = newTint
+ }
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
+ }
+ }
+ }
+}
+
+@VisibleForTesting
+interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding {
+ val iconTint: Int
+ val decorTint: Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 1528c9befda7..1414150c5511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -159,7 +159,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void showNotification(@NonNull NotificationEntry entry) {
mLogger.logShowNotification(entry);
addEntry(entry);
- updateNotification(entry.getKey(), true /* show */);
+ updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
entry.setInterruption();
}
@@ -190,12 +190,12 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
/**
* Called when the notification state has been updated.
* @param key the key of the entry that was updated
- * @param show whether the notification should show again and force reevaluation of
- * removal time
+ * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
+ * of removal time
*/
- public void updateNotification(@NonNull String key, boolean show) {
+ public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+ mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
// with the groupmanager
@@ -204,7 +204,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- if (show) {
+ if (shouldHeadsUpAgain) {
headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
if (headsUpEntry != null) {
setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index b8c7e202ce7c..a7352be8d80a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -182,7 +182,7 @@ interface HeadsUpManager : Dumpable {
*/
fun unpinAll(userUnPinned: Boolean)
- fun updateNotification(key: String, alert: Boolean)
+ fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
}
/** Sets the animation state of the HeadsUpManager. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
new file mode 100644
index 000000000000..970cc75bbb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A controller which provides the current sensitive notification protections status as well as
+ * to assist in feature usage and exemptions
+ */
+public interface SensitiveNotificationProtectionController {
+ /**
+ * Register a runnable that triggers on changes to protection state
+ *
+ * <p> onSensitiveStateChanged not invoked on registration
+ */
+ void registerSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+ /** Unregister a previously registered onSensitiveStateChanged runnable */
+ void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+ /** Return {@code true} if device in state in which notifications should be protected */
+ boolean isSensitiveStateActive();
+
+ /** Return {@code true} when notification should be protected */
+ boolean shouldProtectNotification(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
new file mode 100644
index 000000000000..3c4ca4465874
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.screenshareNotificationHiding;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.ListenerSet;
+
+import javax.inject.Inject;
+
+/** Implementation of SensitiveNotificationProtectionController. **/
+@SysUISingleton
+public class SensitiveNotificationProtectionControllerImpl
+ implements SensitiveNotificationProtectionController {
+ private final MediaProjectionManager mMediaProjectionManager;
+ private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
+ private volatile MediaProjectionInfo mProjection;
+
+ @VisibleForTesting
+ final MediaProjectionManager.Callback mMediaProjectionCallback =
+ new MediaProjectionManager.Callback() {
+ @Override
+ public void onStart(MediaProjectionInfo info) {
+ Trace.beginSection(
+ "SNPC.onProjectionStart");
+ mProjection = info;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo info) {
+ Trace.beginSection(
+ "SNPC.onProjectionStop");
+ mProjection = null;
+ mListeners.forEach(Runnable::run);
+ Trace.endSection();
+ }
+ };
+
+ @Inject
+ public SensitiveNotificationProtectionControllerImpl(
+ MediaProjectionManager mediaProjectionManager,
+ @Main Handler mainHandler) {
+ mMediaProjectionManager = mediaProjectionManager;
+
+ if (screenshareNotificationHiding()) {
+ mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+ }
+ }
+
+ @Override
+ public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
+ mListeners.addIfAbsent(onSensitiveStateChanged);
+ }
+
+ @Override
+ public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) {
+ mListeners.remove(onSensitiveStateChanged);
+ }
+
+ @Override
+ public boolean isSensitiveStateActive() {
+ // TODO(b/316955558): Add disabled by developer option
+ // TODO(b/316955306): Add feature exemption for sysui and bug handlers
+ // TODO(b/316955346): Add feature exemption for single app screen sharing
+ return mProjection != null;
+ }
+
+ @Override
+ public boolean shouldProtectNotification(NotificationEntry entry) {
+ if (!isSensitiveStateActive()) {
+ return false;
+ }
+
+ // Exempt foreground service notifications from protection in effort to keep screen share
+ // stop actions easily accessible
+ // TODO(b/316955208): Exempt FGS notifications only for app that started projection
+ return !entry.getSbn().getNotification().isFgsOrUij();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 3304b9827fd8..15200bd0ac54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
@@ -146,6 +148,11 @@ public interface StatusBarPolicyModule {
/** */
@Binds
+ SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
+ SensitiveNotificationProtectionControllerImpl controllerImpl);
+
+ /** */
+ @Binds
UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
index ce811e205436..10137a0d3430 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt
@@ -17,10 +17,16 @@
package com.android.systemui.statusbar.ui
import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -31,6 +37,8 @@ import kotlinx.coroutines.flow.onStart
class SystemBarUtilsState
@Inject
constructor(
+ @Background bgContext: CoroutineContext,
+ @Main mainContext: CoroutineContext,
configurationController: ConfigurationController,
proxy: SystemBarUtilsProxy,
) {
@@ -38,5 +46,10 @@ constructor(
val statusBarHeight: Flow<Int> =
configurationController.onConfigChanged
.onStart<Any> { emit(Unit) }
+ .flowOn(mainContext)
+ .conflate()
.map { proxy.getStatusBarHeight() }
+ .distinctUntilChanged()
+ .flowOn(bgContext)
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index b8f95832b852..1ba269e25dff 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -24,7 +24,7 @@ import android.content.IntentFilter
import android.os.UserHandle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -54,7 +54,7 @@ interface WallpaperRepository {
class WallpaperRepositoryImpl
@Inject
constructor(
- @Application scope: CoroutineScope,
+ @Background scope: CoroutineScope,
broadcastDispatcher: BroadcastDispatcher,
userRepository: UserRepository,
private val wallpaperManager: WallpaperManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index c2efc05132ba..c2efc05132ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 0959f1b2bcf6..0959f1b2bcf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 1281e4409a83..1281e4409a83 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 5e5273b779c6..bc9a0a5484ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -181,16 +181,16 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Test
public void onAttachedToWindow_menuIsVisible() {
mMenuViewLayer.onAttachedToWindow();
- final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
+ final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
assertThat(menuView.getVisibility()).isEqualTo(VISIBLE);
}
@Test
- public void onAttachedToWindow_menuIsGone() {
+ public void onDetachedFromWindow_menuIsGone() {
mMenuViewLayer.onDetachedFromWindow();
- final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
+ final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
assertThat(menuView.getVisibility()).isEqualTo(GONE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b57cf53911e9..754a7fd81475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -97,6 +97,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
@@ -150,6 +151,7 @@ import kotlinx.coroutines.test.TestScope;
@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardViewMediator mViewMediator;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
@@ -265,7 +267,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags);
+ mSceneContainerFlags,
+ mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 90eaa5a2eeaf..dafd9e64371b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,8 +23,9 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
@@ -34,6 +35,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -42,11 +45,15 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -57,7 +64,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -83,7 +89,8 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class KeyguardTransitionScenariosTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
@@ -119,17 +126,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val testDispatcher = StandardTestDispatcher()
- testScope = TestScope(testDispatcher)
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ bouncerRepository = kosmos.fakeKeyguardBouncerRepository
commandQueue = FakeCommandQueue()
- shadeRepository = FakeShadeRepository()
- transitionRepository = spy(FakeKeyguardTransitionRepository())
+ shadeRepository = kosmos.fakeShadeRepository
+ transitionRepository = spy(kosmos.fakeKeyguardTransitionRepository)
powerInteractor = PowerInteractorFactory.create().powerInteractor
- communalInteractor =
- CommunalInteractorFactory.create(testScope = testScope).communalInteractor
+ communalInteractor = kosmos.communalInteractor
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
@@ -160,8 +164,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -184,8 +188,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromPrimaryBouncerTransitionInteractor =
FromPrimaryBouncerTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -199,8 +203,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -210,8 +214,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromDreamingLockscreenHostedTransitionInteractor =
FromDreamingLockscreenHostedTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -221,8 +225,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -232,8 +236,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromGoneTransitionInteractor =
FromGoneTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -244,8 +248,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromDozingTransitionInteractor =
FromDozingTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -257,8 +261,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromOccludedTransitionInteractor =
FromOccludedTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -269,8 +273,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromAlternateBouncerTransitionInteractor =
FromAlternateBouncerTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
@@ -281,8 +285,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
fromGlanceableHubTransitionInteractor =
FromGlanceableHubTransitionInteractor(
scope = testScope,
- bgDispatcher = testDispatcher,
- mainDispatcher = testDispatcher,
+ bgDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
glanceableHubTransitions = glanceableHubTransitions,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 6be92756ba8a..ba7927d6b8bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -25,8 +25,7 @@ import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
@@ -43,6 +42,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -79,6 +79,8 @@ import org.mockito.junit.MockitoJUnit
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class MediaHierarchyManagerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock private lateinit var lockHost: MediaHost
@Mock private lateinit var qsHost: MediaHost
@Mock private lateinit var qqsHost: MediaHost
@@ -110,10 +112,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val communalRepository =
- FakeCommunalRepository(applicationScope = testScope.backgroundScope)
- private val communalInteractor =
- CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
+ private val communalInteractor = kosmos.communalInteractor
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 0a464e6047d3..b701d7f315bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -21,6 +21,7 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.models.player.MediaViewHolder
@@ -171,6 +172,38 @@ class MediaViewControllerTest : SysuiTestCase() {
}
@Test
+ fun testObtainViewState_expandedMatchesParentHeight() {
+ mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ player.measureState =
+ TransitionViewState().apply {
+ this.height = 100
+ this.measureHeight = 100
+ }
+ mediaHostStateHolder.expandedMatchesParentHeight = true
+ mediaHostStateHolder.expansion = 1f
+ mediaHostStateHolder.measurementInput =
+ MeasurementInput(
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
+ )
+
+ // Assign the height of each expanded layout
+ MediaViewHolder.backgroundIds.forEach { id ->
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 100
+ }
+
+ mediaViewController.obtainViewState(mediaHostStateHolder)
+
+ // Verify height of each expanded layout is updated to match constraint
+ MediaViewHolder.backgroundIds.forEach { id ->
+ assertTrue(
+ mediaViewController.expandedLayout.getConstraint(id).layout.mHeight ==
+ ConstraintSet.MATCH_CONSTRAINT
+ )
+ }
+ }
+
+ @Test
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forMediaPlayer() {
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
whenever(mockCopiedState.widgetStates)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 72847a6b6c45..c6cfabc61300 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -414,6 +414,18 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
+ // This test relies on mMediaOutputController.start being called while the selected device
+ // list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
mMediaOutputController.start(mCb);
reset(mCb);
@@ -434,6 +446,18 @@ public class MediaOutputControllerTest extends SysuiTestCase {
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
+ // This test relies on mMediaOutputController.start being called while the selected device
+ // list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
when(mMediaDevice1.getFeatures()).thenReturn(
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b7a9ea751438..81ff817830ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -25,14 +25,16 @@ import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -52,6 +54,8 @@ import org.mockito.MockitoAnnotations
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class GlanceableHubContainerControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@@ -71,9 +75,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- val withDeps = CommunalInteractorFactory.create()
- communalInteractor = withDeps.communalInteractor
- communalRepository = withDeps.communalRepository
+ communalInteractor = kosmos.communalInteractor
+ communalRepository = kosmos.fakeCommunalRepository
underTest =
GlanceableHubContainerController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 11da2376328a..200e758245a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -57,7 +57,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -202,8 +201,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
new ConfigurationInteractor(configurationRepository),
shadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
@@ -303,7 +301,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags) {
+ mSceneContainerFlags,
+ () -> communalInteractor) {
@Override
protected boolean isDebuggable() {
return false;
@@ -451,6 +450,24 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void setCommunalShowing_userTimeout() {
+ setKeyguardShowing();
+ clearInvocations(mWindowManager);
+
+ mNotificationShadeWindowController.onCommunalShowingChanged(true);
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().userActivityTimeout)
+ .isEqualTo(CommunalInteractor.AWAKE_INTERVAL_MS);
+ clearInvocations(mWindowManager);
+
+ // Bouncer showing over communal overrides communal value
+ mNotificationShadeWindowController.setBouncerShowing(true);
+ verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().userActivityTimeout)
+ .isEqualTo(KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS);
+ }
+
+ @Test
public void setKeyguardShowing_notFocusable_byDefault() {
mNotificationShadeWindowController.setKeyguardShowing(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 8f46a37bf540..6fc88ce95325 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -43,7 +43,6 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
@@ -233,8 +232,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
() -> sceneInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
FakeKeyguardTransitionRepository keyguardTransitionRepository =
new FakeKeyguardTransitionRepository();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 05e866e85112..13934dac2401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,7 +27,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -144,7 +144,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
{ fromLockscreenTransitionInteractor },
{ fromPrimaryBouncerTransitionInteractor }
)
- val communalInteractor = CommunalInteractorFactory.create().communalInteractor
+ val communalInteractor = kosmos.communalInteractor
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index df547ae5883e..350ed2d9ff22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -55,28 +58,31 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
val statusBarStateController: StatusBarStateController = mock()
val keyguardStateController: KeyguardStateController = mock()
val mSelectedUserInteractor: SelectedUserInteractor = mock()
+ val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
+ mock()
val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent
- .factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController,
- mSelectedUserInteractor)
- .coordinator
+ DaggerTestSensitiveContentCoordinatorComponent.factory()
+ .create(
+ dynamicPrivacyController,
+ lockscreenUserManager,
+ keyguardUpdateMonitor,
+ statusBarStateController,
+ keyguardStateController,
+ mSelectedUserInteractor,
+ sensitiveNotificationProtectionController
+ )
+ .coordinator
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
coordinator.attach(pipeline)
- val invalidator = withArgCaptor<Invalidator> {
- verify(pipeline).addPreRenderInvalidator(capture())
- }
- val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
- verify(dynamicPrivacyController).addListener(capture())
- }
+ val invalidator =
+ withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+ val dynamicPrivacyListener =
+ withArgCaptor<DynamicPrivacyController.Listener> {
+ verify(dynamicPrivacyController).addListener(capture())
+ }
val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
invalidator.setInvalidationListener(invalidationListener)
@@ -89,9 +95,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -105,11 +112,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -123,11 +178,59 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -141,17 +244,87 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, false)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
val entry = fakeNotification(1, false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -159,17 +332,92 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, false)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -179,15 +427,37 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ val entry = fakeNotification(1, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(false, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
val entry = fakeNotification(1, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -195,19 +465,96 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ val entry = fakeNotification(1, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+ val entry = fakeNotification(2, true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+ val entry = fakeNotification(2, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+ onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+ verify(entry.representativeEntry!!).setSensitive(true, true)
+ }
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ @Suppress("ktlint:standard:max-line-length")
+ fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() {
+ coordinator.attach(pipeline)
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
+
+ whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+ whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+ whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+ whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+ whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
val entry = fakeNotification(2, true)
+ whenever(
+ sensitiveNotificationProtectionController.shouldProtectNotification(
+ entry.getRepresentativeEntry()
+ )
+ )
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -217,9 +564,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
@Test
fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
coordinator.attach(pipeline)
- val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
- verify(pipeline).addOnBeforeRenderListListener(capture())
- }
+ val onBeforeRenderListListener =
+ withArgCaptor<OnBeforeRenderListListener> {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
whenever(lockscreenUserManager.currentUserId).thenReturn(1)
whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -227,9 +575,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
- .thenReturn(true)
-
+ .thenReturn(true)
val entry = fakeNotification(2, true)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+ whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
+ .thenReturn(true)
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -237,15 +587,11 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
}
private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
- val mockUserHandle = mock<UserHandle>().apply {
- whenever(identifier).thenReturn(notifUserId)
- }
- val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
- whenever(user).thenReturn(mockUserHandle)
- }
- val mockEntry = mock<NotificationEntry>().apply {
- whenever(sbn).thenReturn(mockSbn)
- }
+ val mockUserHandle =
+ mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) }
+ val mockSbn: StatusBarNotification =
+ mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
+ val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) }
whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
whenever(mockEntry.rowExists()).thenReturn(true)
return object : ListEntry("key", 0) {
@@ -268,6 +614,8 @@ interface TestSensitiveContentCoordinatorComponent {
@BindsInstance statusBarStateController: StatusBarStateController,
@BindsInstance keyguardStateController: KeyguardStateController,
@BindsInstance selectedUserInteractor: SelectedUserInteractor,
+ @BindsInstance
+ sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
): TestSensitiveContentCoordinatorComponent
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89f826b2049d..1ab4c32c7d08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -32,6 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -39,6 +41,7 @@ import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDis
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -101,6 +104,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
@@ -172,10 +176,16 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private ActivityStarter mActivityStarter;
@Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
@Mock private NotificationListViewBinder mViewBinder;
+ @Mock
+ private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+
+ @Captor
+ private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+
private final ActiveNotificationListRepository mActiveNotificationsRepository =
new ActiveNotificationListRepository();
@@ -386,6 +396,23 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ public void testOnUserChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnUserChange_verifySensitiveProfile() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -403,6 +430,80 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+ initController(/* viewIsAttached= */ true);
+
+ ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+ .forClass(UserChangedListener.class);
+
+ verify(mNotificationLockscreenUserManager)
+ .addUserChangedListener(userChangedCaptor.capture());
+ reset(mNotificationStackScrollLayout);
+
+ UserChangedListener changedListener = userChangedCaptor.getValue();
+ changedListener.onUserChanged(0);
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
public void testOnStatePostChange_verifyIfProfileIsPublic() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
@@ -418,6 +519,194 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled(
+ ) {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSysuiStatusBarStateController).addCallback(
+ mStateListenerArgumentCaptor.capture(), anyInt());
+
+ StatusBarStateController.StateListener stateListener =
+ mStateListenerArgumentCaptor.getValue();
+
+ stateListener.onStatePostChange();
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyNotSensitive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive())
+ .thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfProfileIsPublic() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void testOnProjectionStateChanged_verifyIfSensitiveActive() {
+ when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+ when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+ mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+ verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+ }
+
+ @Test
public void testOnMenuShownLogging() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
@@ -666,6 +955,20 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
}
+ @Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ public void sensitiveNotificationProtectionControllerListenerRegistered() {
+ initController(/* viewIsAttached= */ true);
+ verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
@@ -744,7 +1047,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
mActivityStarter,
- new ResourcesSplitShadeStateController());
+ new ResourcesSplitShadeStateController(),
+ mSensitiveNotificationProtectionController);
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4827c92ce452..d9eaea1367cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -66,6 +66,7 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -145,6 +146,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private AlternateBouncerToGoneTransitionViewModel
mAlternateBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@Mock private CoroutineDispatcher mMainDispatcher;
@Mock private TypedArray mMockTypedArray;
@@ -292,6 +294,7 @@ public class ScrimControllerTest extends SysuiTestCase {
mPrimaryBouncerToGoneTransitionViewModel,
mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mKeyguardInteractor,
mWallpaperRepository,
mMainDispatcher,
mLinearLargeScreenShadeInterpolator);
@@ -1000,6 +1003,7 @@ public class ScrimControllerTest extends SysuiTestCase {
mPrimaryBouncerToGoneTransitionViewModel,
mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
+ mKeyguardInteractor,
mWallpaperRepository,
mMainDispatcher,
mLinearLargeScreenShadeInterpolator);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 1f8cc54211e6..6785de930849 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -168,6 +168,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn)
// TODO(b/261029387): check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
@@ -194,6 +195,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
val roaming: Boolean,
val name: String,
val slice: Boolean,
+ val ntn: Boolean,
) {
override fun toString(): String {
return "INPUT(level=$level, " +
@@ -205,7 +207,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
"carrierNetworkChange=$carrierNetworkChange, " +
"roaming=$roaming, " +
"name=$name," +
- "slice=$slice)"
+ "slice=$slice" +
+ "ntn=$ntn)"
}
// Convenience for iterating test data and creating new cases
@@ -220,6 +223,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
roaming: Boolean? = null,
name: String? = null,
slice: Boolean? = null,
+ ntn: Boolean? = null,
): TestCase =
TestCase(
level = level ?: this.level,
@@ -232,6 +236,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
roaming = roaming ?: this.roaming,
name = name ?: this.name,
slice = slice ?: this.slice,
+ ntn = ntn ?: this.ntn,
)
}
@@ -262,6 +267,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
private val roaming = listOf(false, true)
private val names = listOf("name 1", "name 2")
private val slice = listOf(false, true)
+ private val ntn = listOf(false, true)
@Parameters(name = "{0}") @JvmStatic fun data() = testData()
@@ -300,6 +306,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
roaming.first(),
names.first(),
slice.first(),
+ ntn.first(),
)
val tail =
@@ -312,7 +319,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
roaming.map { baseCase.modifiedBy(roaming = it) },
names.map { baseCase.modifiedBy(name = it) },
- slice.map { baseCase.modifiedBy(slice = it) }
+ slice.map { baseCase.modifiedBy(slice = it) },
+ ntn.map { baseCase.modifiedBy(ntn = it) }
)
.flatten()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 03814bdfa430..b958f35c0e53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -579,6 +579,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(conn.carrierName.value)
.isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.value).isEqualTo(model.ntn)
// TODO(b/261029387) check these once we start handling them
assertThat(conn.isEmergencyOnly.value).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 9d6f3156f83e..98556514f8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
+import android.platform.test.annotations.EnableFlags
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
@@ -963,6 +964,31 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ fun isNonTerrestrial_updatesFromServiceState() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isNonTerrestrial)
+
+ // Lambda makes it a little clearer what we are testing IMO
+ val serviceStateCreator = { ntn: Boolean ->
+ mock<ServiceState>().also {
+ whenever(it.isUsingNonTerrestrialNetwork).thenReturn(ntn)
+ }
+ }
+
+ // Starts out false
+ assertThat(latest).isFalse()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(serviceStateCreator(true))
+ assertThat(latest).isTrue()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(serviceStateCreator(false))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
fun numberOfLevels_usesCarrierConfig() =
testScope.runTest {
var latest: Int? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 02e6fd5a9d6e..0e0d4897d667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -35,6 +35,7 @@ import android.telephony.satellite.SatelliteModemStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -87,6 +88,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
Optional.empty(),
dispatcher,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
systemClock,
)
@@ -100,6 +102,45 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun satelliteManagerThrows_checkSupportDoesNotCrash() =
+ testScope.runTest {
+ whenever(satelliteManager.requestIsSatelliteSupported(any(), any()))
+ .thenThrow(IllegalStateException())
+
+ systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME)
+
+ underTest =
+ DeviceBasedSatelliteRepositoryImpl(
+ Optional.of(satelliteManager),
+ dispatcher,
+ testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
+ systemClock,
+ )
+
+ runCurrent()
+
+ // Creating the repo does not crash, and we consider the feature not to be supported
+ assertThat(underTest.satelliteSupport.value).isEqualTo(SatelliteSupport.NotSupported)
+ }
+
+ @Test
+ fun satelliteManagerThrows_doesNotCrash() =
+ testScope.runTest {
+ setupDefaultRepo()
+
+ whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
+ .thenThrow(SatelliteException(13))
+
+ val conn by collectLastValue(underTest.connectionState)
+ val strength by collectLastValue(underTest.signalStrength)
+
+ // Flows have not emitted, we haven't crashed
+ assertThat(conn).isNull()
+ assertThat(strength).isNull()
+ }
+
+ @Test
fun connectionState_mapsFromSatelliteModemState() =
testScope.runTest {
setupDefaultRepo()
@@ -380,6 +421,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
if (satMan != null) Optional.of(satMan) else Optional.empty(),
dispatcher,
testScope.backgroundScope,
+ FakeLogBuffer.Factory.create(),
systemClock,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
new file mode 100644
index 000000000000..21c038ad476d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: DeviceBasedSatelliteViewModel
+ private lateinit var interactor: DeviceBasedSatelliteInteractor
+
+ private val repo = FakeDeviceBasedSatelliteRepository()
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
+ private val testScope = TestScope()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ interactor =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ mobileIconsInteractor,
+ testScope.backgroundScope,
+ )
+
+ underTest =
+ DeviceBasedSatelliteViewModel(
+ interactor,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is not allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // THEN icon is null because we should not be showing it
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_nullWhenShouldNotShow_notAllOos() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are not OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = true
+
+ // THEN icon is null because we have service
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun icon_satelliteIsOff() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // THEN icon is null because we have service
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
new file mode 100644
index 000000000000..ca9df57e8798
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Being a simple subclass of [ModernStatusBarView], use the same basic test cases to verify the
+ * root behavior, and add testing for the new [SingleBindableStatusBarIconView.withDefaultBinding]
+ * method.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SingleBindableStatusBarIconViewTest : SysuiTestCase() {
+ private lateinit var binding: SingleBindableStatusBarIconViewBinding
+
+ // Visibility is outsourced to view-models. This simulates it
+ private var isVisible = true
+ private var visibilityFn: () -> Boolean = { isVisible }
+
+ @Test
+ fun initView_hasCorrectSlot() {
+ val view = createAndInitView()
+
+ assertThat(view.slot).isEqualTo(SLOT_NAME)
+ }
+
+ @Test
+ fun getVisibleState_icon_returnsIcon() {
+ val view = createAndInitView()
+
+ view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_ICON)
+ }
+
+ @Test
+ fun getVisibleState_dot_returnsDot() {
+ val view = createAndInitView()
+
+ view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_DOT)
+ }
+
+ @Test
+ fun getVisibleState_hidden_returnsHidden() {
+ val view = createAndInitView()
+
+ view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_HIDDEN)
+ }
+
+ @Test
+ fun onDarkChanged_bindingReceivesIconAndDecorTint() {
+ val view = createAndInitView()
+
+ view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.decorTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setStaticDrawableColor_bindingReceivesIconTint() {
+ val view = createAndInitView()
+
+ view.setStaticDrawableColor(0x12345678, 0x12344321)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setDecorColor_bindingReceivesDecorColor() {
+ val view = createAndInitView()
+
+ view.setDecorColor(0x23456789)
+
+ assertThat(binding.decorTint).isEqualTo(0x23456789)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_true() {
+ val view = createAndInitView()
+
+ isVisible = true
+
+ assertThat(view.isIconVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_false() {
+ val view = createAndInitView()
+
+ isVisible = false
+
+ assertThat(view.isIconVisible).isEqualTo(false)
+ }
+
+ @Test
+ fun getDrawingRect_takesTranslationIntoAccount() {
+ val view = createAndInitView()
+
+ view.translationX = 50f
+ view.translationY = 60f
+
+ val drawingRect = Rect()
+ view.getDrawingRect(drawingRect)
+
+ assertThat(drawingRect.left).isEqualTo(view.left + 50)
+ assertThat(drawingRect.right).isEqualTo(view.right + 50)
+ assertThat(drawingRect.top).isEqualTo(view.top + 60)
+ assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+ }
+
+ private fun createAndInitView(): SingleBindableStatusBarIconView {
+ val view = SingleBindableStatusBarIconView.createView(context)
+ binding = SingleBindableStatusBarIconView.withDefaultBinding(view, visibilityFn) {}
+ view.initView(SLOT_NAME) { binding }
+ return view
+ }
+
+ companion object {
+ private const val SLOT_NAME = "test_slot"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
new file mode 100644
index 000000000000..cd5d5ed0d08e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+ @Mock private lateinit var handler: Handler
+
+ @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+
+ @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
+
+ @Mock private lateinit var listener1: Runnable
+ @Mock private lateinit var listener2: Runnable
+ @Mock private lateinit var listener3: Runnable
+
+ @Captor
+ private lateinit var mediaProjectionCallbackCaptor:
+ ArgumentCaptor<MediaProjectionManager.Callback>
+
+ private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ // Obtain useful MediaProjectionCallback
+ verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+ }
+
+ @Test
+ fun init_flagEnabled_registerMediaProjectionManagerCallback() {
+ assertNotNull(mediaProjectionCallbackCaptor.value)
+ }
+
+ @Test
+ fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ reset(mediaProjectionManager)
+
+ controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+ verifyZeroInteractions(mediaProjectionManager)
+ }
+
+ @Test
+ fun registerSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ }
+
+ @Test
+ fun registerSensitiveStateListener_afterProjectionActive() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ controller.registerSensitiveStateListener(listener1)
+ verifyZeroInteractions(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1).run()
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_singleListener() {
+ controller.registerSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ }
+
+ @Test
+ fun unregisterSensitiveStateListener_multipleListeners() {
+ controller.registerSensitiveStateListener(listener1)
+ controller.registerSensitiveStateListener(listener2)
+ controller.registerSensitiveStateListener(listener3)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verify(listener1, times(2)).run()
+ verify(listener2, times(2)).run()
+ verify(listener3, times(2)).run()
+
+ controller.unregisterSensitiveStateListener(listener1)
+ controller.unregisterSensitiveStateListener(listener2)
+
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ verifyNoMoreInteractions(listener1)
+ verifyNoMoreInteractions(listener2)
+ verify(listener3, times(4)).run()
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactive_false() {
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+ assertFalse(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ assertTrue(controller.isSensitiveStateActive)
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionInactive_false() {
+ val notificationEntry = mock(NotificationEntry::class.java)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(true)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+ mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+ val notificationEntry = mock(NotificationEntry::class.java)
+ val sbn = mock(StatusBarNotification::class.java)
+ val notification = mock(Notification::class.java)
+ `when`(notificationEntry.sbn).thenReturn(sbn)
+ `when`(sbn.notification).thenReturn(notification)
+ `when`(notification.isFgsOrUij).thenReturn(false)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 589f7c23ea13..93d8dcc8f092 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -100,7 +100,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -443,8 +442,7 @@ public class BubblesTest extends SysuiTestCase {
() -> keyguardInteractor,
() -> mFromLockscreenTransitionInteractor,
() -> mFromPrimaryBouncerTransitionInteractor);
- CommunalInteractor communalInteractor =
- CommunalInteractorFactory.create().getCommunalInteractor();
+ CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
mFromLockscreenTransitionInteractor = new FromLockscreenTransitionInteractor(
keyguardTransitionRepository,
@@ -536,7 +534,8 @@ public class BubblesTest extends SysuiTestCase {
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags
+ mSceneContainerFlags,
+ mKosmos::getCommunalInteractor
);
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index b820ca612bfc..1afe56f27be7 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.os.Looper;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
+import android.util.Singleton;
import android.view.Choreographer;
import com.android.internal.util.Preconditions;
@@ -67,7 +68,12 @@ import java.util.function.Consumer;
public final class AnimatorTestRule implements TestRule {
private final Object mLock = new Object();
- private final TestHandler mTestHandler = new TestHandler();
+ private final Singleton<TestHandler> mTestHandler = new Singleton<>() {
+ @Override
+ protected TestHandler create() {
+ return new TestHandler();
+ }
+ };
private final long mStartTime;
private long mTotalTimeDelta = 0;
@@ -95,16 +101,17 @@ public final class AnimatorTestRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
- AnimationHandler objAtStart = AnimationHandler.setTestHandler(mTestHandler);
+ final TestHandler testHandler = mTestHandler.get();
+ AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler);
try {
base.evaluate();
} finally {
AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart);
- if (mTestHandler != objAtEnd) {
+ if (testHandler != objAtEnd) {
// pass or fail, inner logic not restoring the handler needs to be reported.
// noinspection ThrowFromFinallyBlock
throw new IllegalStateException("Test handler was altered: expected="
- + mTestHandler + " actual=" + objAtEnd);
+ + testHandler + " actual=" + objAtEnd);
}
}
}
@@ -125,8 +132,9 @@ public final class AnimatorTestRule implements TestRule {
public void initNewAnimators() {
requireLooper("AnimationTestRule#initNewAnimators()");
long currentTime = getCurrentTime();
- List<AnimationFrameCallback> newCallbacks = new ArrayList<>(mTestHandler.mNewCallbacks);
- mTestHandler.mNewCallbacks.clear();
+ final TestHandler testHandler = mTestHandler.get();
+ List<AnimationFrameCallback> newCallbacks = new ArrayList<>(testHandler.mNewCallbacks);
+ testHandler.mNewCallbacks.clear();
for (AnimationFrameCallback newCallback : newCallbacks) {
newCallback.doAnimationFrame(currentTime);
}
@@ -158,9 +166,10 @@ public final class AnimatorTestRule implements TestRule {
public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) {
Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative");
requireLooper("AnimationTestRule#advanceTimeBy(long)");
+ final TestHandler testHandler = mTestHandler.get();
if (timeDelta == 0) {
// If time is not being advanced, all animators will get a tick; don't double tick these
- mTestHandler.mNewCallbacks.clear();
+ testHandler.mNewCallbacks.clear();
} else {
// before advancing time, start new animators with the current time
initNewAnimators();
@@ -172,10 +181,10 @@ public final class AnimatorTestRule implements TestRule {
if (preFrameAction != null) {
preFrameAction.accept(timeDelta);
// After letting other code run, clear any new callbacks to avoid double-ticking them
- mTestHandler.mNewCallbacks.clear();
+ testHandler.mNewCallbacks.clear();
}
// produce a frame
- mTestHandler.doFrame();
+ testHandler.doFrame();
}
/**
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
deleted file mode 100644
index 1ba863052e2d..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.communal.domain.interactor
-
-import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
-import com.android.systemui.communal.data.repository.FakeCommunalRepository
-import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
-import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.test.TestScope
-
-// TODO(b/319335645): get rid of me and use kosmos.
-object CommunalInteractorFactory {
-
- @JvmOverloads
- @JvmStatic
- fun create(
- testScope: TestScope = TestScope(),
- communalRepository: FakeCommunalRepository =
- FakeCommunalRepository(testScope.backgroundScope),
- keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
- widgetRepository: FakeCommunalWidgetRepository =
- FakeCommunalWidgetRepository(testScope.backgroundScope),
- mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
- smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
- tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
- communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
- appWidgetHost: CommunalAppWidgetHost = mock(),
- editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
- ): WithDependencies {
- val keyguardInteractor =
- KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
- val communalTutorialInteractor =
- CommunalTutorialInteractor(
- testScope.backgroundScope,
- tutorialRepository,
- keyguardInteractor,
- communalRepository,
- )
-
- return WithDependencies(
- testScope,
- communalRepository,
- widgetRepository,
- communalPrefsRepository,
- mediaRepository,
- smartspaceRepository,
- tutorialRepository,
- keyguardRepository,
- keyguardInteractor,
- communalTutorialInteractor,
- appWidgetHost,
- editWidgetsActivityStarter,
- CommunalInteractor(
- testScope.backgroundScope,
- communalRepository,
- widgetRepository,
- communalPrefsRepository,
- mediaRepository,
- smartspaceRepository,
- keyguardInteractor,
- appWidgetHost,
- editWidgetsActivityStarter,
- ),
- )
- }
-
- data class WithDependencies(
- val testScope: TestScope,
- val communalRepository: FakeCommunalRepository,
- val widgetRepository: FakeCommunalWidgetRepository,
- val communalPrefsRepository: FakeCommunalPrefsRepository,
- val mediaRepository: FakeCommunalMediaRepository,
- val smartspaceRepository: FakeSmartspaceRepository,
- val tutorialRepository: FakeCommunalTutorialRepository,
- val keyguardRepository: FakeKeyguardRepository,
- val keyguardInteractor: KeyguardInteractor,
- val tutorialInteractor: CommunalTutorialInteractor,
- val appWidgetHost: CommunalAppWidgetHost,
- val editWidgetsActivityStarter: EditWidgetsActivityStarter,
- val communalInteractor: CommunalInteractor,
- )
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 65579a6d9ddf..1abf71fde14c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -20,11 +20,13 @@ import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
+import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
@@ -35,8 +37,11 @@ val Kosmos.communalInteractor by Fixture {
mediaRepository = communalMediaRepository,
communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
+ userRepository = userRepository,
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
- editWidgetsActivityStarter = mock(),
+ editWidgetsActivityStarter = editWidgetsActivityStarter,
)
}
+
+val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index baba88db8e55..adaea7cbf64d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -29,5 +29,6 @@ val Kosmos.communalTutorialInteractor by
communalTutorialRepository = communalTutorialRepository,
keyguardInteractor = keyguardInteractor,
communalRepository = communalRepository,
+ communalInteractor = communalInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 59f56dd18f0e..5766f7a9028c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -130,6 +130,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
private val _isEncryptedOrLockdown = MutableStateFlow(true)
override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+ override val topClippingBounds = MutableStateFlow<Int?>(null)
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
deleted file mode 100644
index 92193fd6bc06..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ /dev/null
@@ -1,47 +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.keyguard.domain.interactor
-
-import com.android.systemui.communal.data.repository.communalMediaRepository
-import com.android.systemui.communal.data.repository.communalPrefsRepository
-import com.android.systemui.communal.data.repository.communalRepository
-import com.android.systemui.communal.data.repository.communalWidgetRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.smartspace.data.repository.smartspaceRepository
-import org.mockito.Mockito.mock
-
-val Kosmos.communalInteractor by
- Kosmos.Fixture {
- CommunalInteractor(
- applicationScope = applicationCoroutineScope,
- communalRepository = communalRepository,
- widgetRepository = communalWidgetRepository,
- mediaRepository = communalMediaRepository,
- communalPrefsRepository = communalPrefsRepository,
- smartspaceRepository = smartspaceRepository,
- keyguardInteractor = keyguardInteractor,
- appWidgetHost = mock(CommunalAppWidgetHost::class.java),
- editWidgetsActivityStarter = editWidgetsActivityStarter,
- )
- }
-
-val Kosmos.editWidgetsActivityStarter by
- Kosmos.Fixture<EditWidgetsActivityStarter> { mock(EditWidgetsActivityStarter::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index 294b5ba474c3..ec17c488e297 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index cc0449d7e7bb..321f94468a2e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -26,6 +26,7 @@ import com.android.systemui.classifier.falsingCollector
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -72,6 +73,7 @@ class KosmosJavaAdapter(
val powerInteractor by lazy { kosmos.powerInteractor }
val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+ val communalInteractor by lazy { kosmos.communalInteractor }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index a9c8ec7dcb7d..32d572ef9dee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -35,6 +35,7 @@ class FakeMobileConnectionRepository(
override val isRoaming = MutableStateFlow(false)
override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
override val isInService = MutableStateFlow(false)
+ override val isNonTerrestrial = MutableStateFlow(false)
override val isGsm = MutableStateFlow(false)
override val cdmaLevel = MutableStateFlow(0)
override val primaryLevel = MutableStateFlow(0)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
index e208add32efa..5476d5509c0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt
@@ -17,7 +17,15 @@
package com.android.systemui.statusbar.ui
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.policy.configurationController
-val Kosmos.systemBarUtilsState by
- Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) }
+val Kosmos.systemBarUtilsState by Fixture {
+ SystemBarUtilsState(
+ testDispatcher,
+ testDispatcher,
+ configurationController,
+ systemBarUtilsProxy,
+ )
+}
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index b5130a1c4cfc..ced10fbeff0c 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -34,3 +34,10 @@ flag {
description: "Mitigation for autofill providers miscalculating view visibility"
bug: "291795358"
}
+
+flag {
+ name: "remote_fill_service_use_weak_reference"
+ namespace: "autofill"
+ description: "Use weak reference to address binder leak problem"
+ bug: "307972253"
+}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 6d0915bf7d92..5c93991bef8c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -17,6 +17,7 @@
package com.android.server.autofill;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.service.autofill.Flags.remoteFillServiceUseWeakReference;
import static com.android.server.autofill.Helper.sVerbose;
@@ -44,6 +45,7 @@ import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.IResultReceiver;
+import java.lang.ref.WeakReference;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -65,6 +67,8 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
private final Object mLock = new Object();
private CompletableFuture<FillResponse> mPendingFillRequest;
private int mPendingFillRequestId = INVALID_REQUEST_ID;
+ private AtomicReference<IFillCallback> mFillCallback;
+ private AtomicReference<ISaveCallback> mSaveCallback;
private final ComponentName mComponentName;
private final boolean mIsCredentialAutofillService;
@@ -150,6 +154,89 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}
}
+ static class IFillCallbackDelegate extends IFillCallback.Stub {
+ private WeakReference<IFillCallback> mCallbackWeakRef;
+
+ IFillCallbackDelegate(IFillCallback callback) {
+ mCallbackWeakRef = new WeakReference(callback);
+ }
+
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) throws RemoteException {
+ IFillCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onCancellable(cancellation);
+ }
+ }
+
+ @Override
+ public void onSuccess(FillResponse response) throws RemoteException {
+ IFillCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onSuccess(response);
+ }
+ }
+
+ @Override
+ public void onFailure(int requestId, CharSequence message) throws RemoteException {
+ IFillCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onFailure(requestId, message);
+ }
+ }
+ }
+
+ static class ISaveCallbackDelegate extends ISaveCallback.Stub {
+
+ private WeakReference<ISaveCallback> mCallbackWeakRef;
+
+ ISaveCallbackDelegate(ISaveCallback callback) {
+ mCallbackWeakRef = new WeakReference(callback);
+ }
+
+ @Override
+ public void onSuccess(IntentSender intentSender) throws RemoteException {
+ ISaveCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onSuccess(intentSender);
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence message) throws RemoteException {
+ ISaveCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onFailure(message);
+ }
+ }
+ }
+
+ /**
+ * Wraps an {@link IFillCallback} object using weak reference.
+ *
+ * This prevents lingering allocation issue by breaking the chain of strong references from
+ * Binder to {@link callback}. Since {@link callback} is not held by Binder anymore, it needs
+ * to be held by {@link mFillCallback} so it's not deallocated prematurely.
+ */
+ private IFillCallback maybeWrapWithWeakReference(IFillCallback callback) {
+ if (remoteFillServiceUseWeakReference()) {
+ mFillCallback = new AtomicReference<>(callback);
+ return new IFillCallbackDelegate(callback);
+ }
+ return callback;
+ }
+
+ /**
+ * Wraps an {@link ISaveCallback} object using weak reference.
+ */
+ private ISaveCallback maybeWrapWithWeakReference(ISaveCallback callback) {
+ if (remoteFillServiceUseWeakReference()) {
+ mSaveCallback = new AtomicReference<>(callback);
+ return new ISaveCallbackDelegate(callback);
+ }
+ return callback;
+ }
+
public void onFillCredentialRequest(@NonNull FillRequest request,
IAutoFillManagerClient autofillCallback) {
if (sVerbose) {
@@ -164,29 +251,30 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}
CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
- remoteService.onFillCredentialRequest(request, new IFillCallback.Stub() {
- @Override
- public void onCancellable(ICancellationSignal cancellation) {
- CompletableFuture<FillResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellation);
- } else {
- cancellationSink.set(cancellation);
- }
- }
-
- @Override
- public void onSuccess(FillResponse response) {
- fillRequest.complete(response);
- }
-
- @Override
- public void onFailure(int requestId, CharSequence message) {
- String errorMessage = message == null ? "" : String.valueOf(message);
- fillRequest.completeExceptionally(
- new RuntimeException(errorMessage));
- }
- }, autofillCallback);
+ remoteService.onFillCredentialRequest(
+ request, maybeWrapWithWeakReference(new IFillCallback.Stub() {
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<FillResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ }
+ }
+
+ @Override
+ public void onSuccess(FillResponse response) {
+ fillRequest.complete(response);
+ }
+
+ @Override
+ public void onFailure(int requestId, CharSequence message) {
+ String errorMessage = message == null ? "" : String.valueOf(message);
+ fillRequest.completeExceptionally(
+ new RuntimeException(errorMessage));
+ }
+ }), autofillCallback);
return fillRequest;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenFillRequest);
@@ -235,29 +323,30 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}
CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
- remoteService.onFillRequest(request, new IFillCallback.Stub() {
- @Override
- public void onCancellable(ICancellationSignal cancellation) {
- CompletableFuture<FillResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellation);
- } else {
- cancellationSink.set(cancellation);
- }
- }
-
- @Override
- public void onSuccess(FillResponse response) {
- fillRequest.complete(response);
- }
-
- @Override
- public void onFailure(int requestId, CharSequence message) {
- String errorMessage = message == null ? "" : String.valueOf(message);
- fillRequest.completeExceptionally(
- new RuntimeException(errorMessage));
- }
- });
+ remoteService.onFillRequest(
+ request, maybeWrapWithWeakReference(new IFillCallback.Stub() {
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<FillResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ }
+ }
+
+ @Override
+ public void onSuccess(FillResponse response) {
+ fillRequest.complete(response);
+ }
+
+ @Override
+ public void onFailure(int requestId, CharSequence message) {
+ String errorMessage = message == null ? "" : String.valueOf(message);
+ fillRequest.completeExceptionally(
+ new RuntimeException(errorMessage));
+ }
+ }));
return fillRequest;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenFillRequest);
@@ -294,7 +383,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
if (sVerbose) Slog.v(TAG, "calling onSaveRequest()");
CompletableFuture<IntentSender> save = new CompletableFuture<>();
- service.onSaveRequest(request, new ISaveCallback.Stub() {
+ service.onSaveRequest(request, maybeWrapWithWeakReference(new ISaveCallback.Stub() {
@Override
public void onSuccess(IntentSender intentSender) {
save.complete(intentSender);
@@ -304,7 +393,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
public void onFailure(CharSequence message) {
save.completeExceptionally(new RuntimeException(String.valueOf(message)));
}
- });
+ }));
return save;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> Handler.getMain().post(() -> {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 50e18628852d..84e1d9062fd5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -247,6 +247,11 @@ public class CompanionDeviceManagerService extends SystemService {
final Context context = getContext();
mPersistentStore = new PersistentDataStore();
+ mAssociationRequestsProcessor = new AssociationRequestsProcessor(
+ /* cdmService */ this, mAssociationStore);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(
+ /* cdmService */ this, mAssociationStore, mPersistentStore,
+ mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
loadAssociationsFromDisk();
mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -254,17 +259,12 @@ public class CompanionDeviceManagerService extends SystemService {
mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
mAssociationStore, mDevicePresenceCallback);
- mAssociationRequestsProcessor = new AssociationRequestsProcessor(
- /* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mDevicePresenceMonitor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
- mBackupRestoreProcessor = new BackupRestoreProcessor(
- /* cdmService */ this, mAssociationStore, mPersistentStore,
- mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 3483c1a1404a..a493d7a57500 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -95,6 +95,7 @@ public class SystemConfig {
private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
private static final int ALLOW_VENDOR_APEX = 0x400;
+ private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800;
private static final int ALLOW_ALL = ~0;
// property for runtime configuration differentiation
@@ -597,7 +598,7 @@ public class SystemConfig {
// Vendors are only allowed to customize these
int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
- | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
+ | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) {
// For backward compatibility
vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -649,9 +650,9 @@ public class SystemConfig {
// TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited
// the use of hidden APIs from the product partition.
int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS
- | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING
- | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS
- | ALLOW_VENDOR_APEX;
+ | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS
+ | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS
+ | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX;
if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
// TODO(b/157393157): This must check product interface enforcement instead of
// DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement.
@@ -772,6 +773,8 @@ public class SystemConfig {
final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
!= 0;
+ final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS)
+ != 0;
final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
!= 0;
@@ -1246,6 +1249,38 @@ public class SystemConfig {
XmlUtils.skipCurrentTag(parser);
}
} break;
+ case "signature-permissions": {
+ if (allowSignaturePermissions) {
+ // signature permissions from system, apex, vendor, product and
+ // system_ext partitions are stored separately. This is to
+ // prevent xml files in the vendor partition from granting
+ // permissions to signature apps in the system partition and vice versa.
+ boolean vendor = permFile.toPath().startsWith(
+ Environment.getVendorDirectory().toPath() + "/")
+ || permFile.toPath().startsWith(
+ Environment.getOdmDirectory().toPath() + "/");
+ boolean product = permFile.toPath().startsWith(
+ Environment.getProductDirectory().toPath() + "/");
+ boolean systemExt = permFile.toPath().startsWith(
+ Environment.getSystemExtDirectory().toPath() + "/");
+ if (vendor) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getVendorSignatureAppAllowlist());
+ } else if (product) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getProductSignatureAppAllowlist());
+ } else if (systemExt) {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getSystemExtSignatureAppAllowlist());
+ } else {
+ readSignatureAppPermissions(parser,
+ mPermissionAllowlist.getSignatureAppAllowlist());
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } break;
case "oem-permissions": {
if (allowOemPermissions) {
readOemPermissions(parser);
@@ -1655,6 +1690,12 @@ public class SystemConfig {
readPermissionAllowlist(parser, allowlist, "privapp-permissions");
}
+ private void readSignatureAppPermissions(@NonNull XmlPullParser parser,
+ @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist)
+ throws IOException, XmlPullParserException {
+ readPermissionAllowlist(parser, allowlist, "signature-permissions");
+ }
+
private void readInstallInUserType(XmlPullParser parser,
Map<String, Set<String>> doInstallMap,
Map<String, Set<String>> nonInstallMap)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9bd0b4c4648..86894fd9b405 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4830,11 +4830,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!mConstants.mEnableWaitForFinishAttachApplication) {
finishAttachApplicationInner(startSeq, callingUid, pid);
}
-
- // Temporarily disable sending BOOT_COMPLETED to see if this was impacting perf tests
- if (false) {
- maybeSendBootCompletedLocked(app);
- }
+ maybeSendBootCompletedLocked(app);
} catch (Exception e) {
// We need kill the process group here. (b/148588589)
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c96c2ff4f2eb..3487ae3c6e6c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -151,6 +151,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -653,7 +654,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
try {
future.get();
return;
- } catch (ExecutionException e) {
+ } catch (ExecutionException | CancellationException e) {
return;
} catch (InterruptedException e) {
// Keep looping
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4cbee2b89bb2..9f7c07e0af35 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -18,9 +18,6 @@ package com.android.server.audio;
import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
-import static android.media.audio.Flags.autoPublicVolumeApiHardening;
-import static android.media.audio.Flags.automaticBtDeviceType;
-import static android.media.audio.Flags.focusFreezeTestApi;
import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -33,6 +30,9 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.focusFreezeTestApi;
import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
@@ -116,7 +116,6 @@ import android.media.AudioProfile;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
-import android.media.AudioTrack;
import android.media.BluetoothProfileConnectionInfo;
import android.media.FadeManagerConfiguration;
import android.media.IAudioDeviceVolumeDispatcher;
@@ -144,7 +143,7 @@ import android.media.IStrategyNonDefaultDevicesDispatcher;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IStreamAliasingDispatcher;
import android.media.IVolumeController;
-import android.media.LoudnessCodecConfigurator;
+import android.media.LoudnessCodecController;
import android.media.LoudnessCodecInfo;
import android.media.MediaCodec;
import android.media.MediaMetrics;
@@ -10737,34 +10736,35 @@ public class AudioService extends IAudioService.Stub
mLoudnessCodecHelper.unregisterLoudnessCodecUpdatesDispatcher(dispatcher);
}
- /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+ /** @see LoudnessCodecController#create(int) */
@Override
- public void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
- mLoudnessCodecHelper.startLoudnessCodecUpdates(piid, codecInfoList);
+ public void startLoudnessCodecUpdates(int sessionId) {
+ mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
}
- /** @see LoudnessCodecConfigurator#setAudioTrack(AudioTrack) */
+ /** @see LoudnessCodecController#release() */
@Override
- public void stopLoudnessCodecUpdates(int piid) {
- mLoudnessCodecHelper.stopLoudnessCodecUpdates(piid);
+ public void stopLoudnessCodecUpdates(int sessionId) {
+ mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
}
- /** @see LoudnessCodecConfigurator#addMediaCodec(MediaCodec) */
+ /** @see LoudnessCodecController#addMediaCodec(MediaCodec) */
@Override
- public void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo codecInfo) {
- mLoudnessCodecHelper.addLoudnessCodecInfo(piid, mediaCodecHash, codecInfo);
+ public void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ LoudnessCodecInfo codecInfo) {
+ mLoudnessCodecHelper.addLoudnessCodecInfo(sessionId, mediaCodecHash, codecInfo);
}
- /** @see LoudnessCodecConfigurator#removeMediaCodec(MediaCodec) */
+ /** @see LoudnessCodecController#removeMediaCodec(MediaCodec) */
@Override
- public void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
- mLoudnessCodecHelper.removeLoudnessCodecInfo(piid, codecInfo);
+ public void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) {
+ mLoudnessCodecHelper.removeLoudnessCodecInfo(sessionId, codecInfo);
}
- /** @see LoudnessCodecConfigurator#getLoudnessCodecParams(AudioTrack, MediaCodec) */
+ /** @see LoudnessCodecController#getLoudnessCodecParams(MediaCodec) */
@Override
- public PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
- return mLoudnessCodecHelper.getLoudnessParams(piid, codecInfo);
+ public PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
+ return mLoudnessCodecHelper.getLoudnessParams(codecInfo);
}
//==========================================================================================
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index 9b0afc4282a2..01f770b1e89f 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -30,6 +30,8 @@ import static android.media.audio.Flags.automaticBtDeviceType;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager.AudioDeviceCategory;
import android.media.AudioPlaybackConfiguration;
@@ -44,7 +46,6 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Log;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
@@ -59,7 +60,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -75,9 +78,9 @@ public class LoudnessCodecHelper {
/**
* Property containing a string to set for a custom built in speaker SPL range as defined by
* CTA2075. The options that can be set are:
- * - "small": for max SPL with test signal < 75 dB,
- * - "medium": for max SPL with test signal between 70 and 90 dB,
- * - "large": for max SPL with test signal > 85 dB.
+ * - "small": for max SPL with test signal < 75 dB,
+ * - "medium": for max SPL with test signal between 70 and 90 dB,
+ * - "large": for max SPL with test signal > 85 dB.
*/
private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE =
"audio.loudness.builtin-speaker-spl-range-size";
@@ -99,11 +102,13 @@ public class LoudnessCodecHelper {
SPL_RANGE_LARGE
})
@Retention(RetentionPolicy.SOURCE)
- public @interface DeviceSplRange {}
+ public @interface DeviceSplRange {
+ }
private static final class LoudnessRemoteCallbackList extends
RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> {
private final LoudnessCodecHelper mLoudnessCodecHelper;
+
LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) {
mLoudnessCodecHelper = loudnessCodecHelper;
}
@@ -133,9 +138,15 @@ public class LoudnessCodecHelper {
private final Object mLock = new Object();
- /** Contains for each started piid the set corresponding to unique registered audio codecs. */
+ /** Contains for each started track id the known started piids. */
+ @GuardedBy("mLock")
+ private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids =
+ new HashMap<>();
+
+ /** Contains for each LoudnessTrackId a set of started coudec infos. */
@GuardedBy("mLock")
- private final SparseArray<Set<LoudnessCodecInfo>> mStartedPiids = new SparseArray<>();
+ private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo =
+ new HashMap<>();
/** Contains the current device id assignment for each piid. */
@GuardedBy("mLock")
@@ -169,10 +180,12 @@ public class LoudnessCodecHelper {
mMetadataType = metadataType;
return this;
}
+
Builder setIsDownmixing(boolean isDownmixing) {
mIsDownmixing = isDownmixing;
return this;
}
+
Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) {
mDeviceSplRange = deviceSplRange;
return this;
@@ -185,8 +198,8 @@ public class LoudnessCodecHelper {
}
private LoudnessCodecInputProperties(int metadataType,
- boolean isDownmixing,
- @DeviceSplRange int deviceSplRange) {
+ boolean isDownmixing,
+ @DeviceSplRange int deviceSplRange) {
mMetadataType = metadataType;
mIsDownmixing = isDownmixing;
mDeviceSplRange = deviceSplRange;
@@ -273,6 +286,50 @@ public class LoudnessCodecHelper {
}
}
+ /**
+ * Contains the properties necessary to identify the tracks that are receiving annotated
+ * loudness data.
+ **/
+ @VisibleForTesting
+ static final class LoudnessTrackId {
+ private final int mSessionId;
+
+ private final int mPid;
+
+ private LoudnessTrackId(int sessionId, int pid) {
+ mSessionId = sessionId;
+ mPid = pid;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ // type check and cast
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final LoudnessTrackId lti = (LoudnessTrackId) obj;
+ return mSessionId == lti.mSessionId && mPid == lti.mPid;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSessionId, mPid);
+ }
+
+ @Override
+ public String toString() {
+ return "Loudness track id:"
+ + " session ID: " + mSessionId
+ + " pid: " + mPid;
+ }
+ }
+
@GuardedBy("mLock")
private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties =
new HashMap<>();
@@ -290,120 +347,160 @@ public class LoudnessCodecHelper {
mLoudnessUpdateDispatchers.unregister(dispatcher);
}
- void startLoudnessCodecUpdates(int piid, List<LoudnessCodecInfo> codecInfoList) {
+ void startLoudnessCodecUpdates(int sessionId) {
+ int pid = Binder.getCallingPid();
if (DEBUG) {
- Log.d(TAG, "startLoudnessCodecUpdates: piid " + piid + " codecInfos " + codecInfoList);
+ Log.d(TAG,
+ "startLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
}
+ final LoudnessTrackId newConfig = new LoudnessTrackId(sessionId, pid);
+ HashSet<Integer> newPiids;
synchronized (mLock) {
- if (mStartedPiids.contains(piid)) {
- Log.w(TAG, "Already started loudness updates for piid " + piid);
+ if (mStartedConfigInfo.containsKey(newConfig)) {
+ Log.w(TAG, "Already started loudness updates for config: " + newConfig);
return;
}
- Set<LoudnessCodecInfo> infoSet = new HashSet<>(codecInfoList);
- mStartedPiids.put(piid, infoSet);
-
- int pid = Binder.getCallingPid();
- mPiidToPidCache.put(piid, pid);
- sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
+ mStartedConfigInfo.put(newConfig, new HashSet<>());
+ newPiids = new HashSet<>();
+ mStartedConfigPiids.put(newConfig, newPiids);
}
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
mAudioService.getActivePlaybackConfigurations().stream().filter(
- conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
- this::updateCodecParametersForConfiguration);
+ conf -> conf.getSessionId() == sessionId
+ && conf.getClientPid() == pid).forEach(apc -> {
+ int piid = apc.getPlayerInterfaceId();
+ synchronized (mLock) {
+ newPiids.add(piid);
+ mPiidToPidCache.put(piid, pid);
+ sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid));
+ }
+ });
}
}
- void stopLoudnessCodecUpdates(int piid) {
+ void stopLoudnessCodecUpdates(int sessionId) {
+ int pid = Binder.getCallingPid();
if (DEBUG) {
- Log.d(TAG, "stopLoudnessCodecUpdates: piid " + piid);
+ Log.d(TAG,
+ "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid);
}
+ final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
synchronized (mLock) {
- if (!mStartedPiids.contains(piid)) {
- Log.w(TAG, "Loudness updates are already stopped for piid " + piid);
+ if (!mStartedConfigInfo.containsKey(config)) {
+ Log.w(TAG, "Loudness updates are already stopped config: " + config);
return;
}
- mStartedPiids.remove(piid);
- sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
- mPiidToDeviceIdCache.delete(piid);
- mPiidToPidCache.delete(piid);
+ final Set<Integer> startedPiidSet = mStartedConfigPiids.get(config);
+ if (startedPiidSet == null) {
+ Log.e(TAG, "Loudness updates are already stopped config: " + config);
+ return;
+ }
+ for (Integer piid : startedPiidSet) {
+ sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1)));
+ mPiidToDeviceIdCache.delete(piid);
+ mPiidToPidCache.delete(piid);
+ }
+ mStartedConfigPiids.remove(config);
+ mStartedConfigInfo.remove(config);
}
}
- void addLoudnessCodecInfo(int piid, int mediaCodecHash, LoudnessCodecInfo info) {
+ void addLoudnessCodecInfo(int sessionId, int mediaCodecHash,
+ LoudnessCodecInfo info) {
+ int pid = Binder.getCallingPid();
if (DEBUG) {
- Log.d(TAG, "addLoudnessCodecInfo: piid " + piid + " mcHash " + mediaCodecHash + " info "
- + info);
+ Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId
+ + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid);
}
+ final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
Set<LoudnessCodecInfo> infoSet;
+ Set<Integer> piids;
synchronized (mLock) {
- if (!mStartedPiids.contains(piid)) {
- Log.w(TAG, "Cannot add new loudness info for stopped piid " + piid);
+ if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+ config)) {
+ Log.w(TAG, "Cannot add new loudness info for stopped config " + config);
return;
}
- infoSet = mStartedPiids.get(piid);
+ piids = mStartedConfigPiids.get(config);
+ infoSet = mStartedConfigInfo.get(config);
infoSet.add(info);
}
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- mAudioService.getActivePlaybackConfigurations().stream().filter(
- conf -> conf.getPlayerInterfaceId() == piid).findFirst().ifPresent(
- apc -> {
- final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
- if (deviceInfo != null) {
- PersistableBundle updateBundle = new PersistableBundle();
- synchronized (mLock) {
- updateBundle.putPersistableBundle(
- Integer.toString(mediaCodecHash),
- getCodecBundle_l(deviceInfo, info));
- }
- if (!updateBundle.isDefinitelyEmpty()) {
- dispatchNewLoudnessParameters(piid, updateBundle);
- }
- }
- });
+ final PersistableBundle updateBundle = new PersistableBundle();
+ Optional<AudioPlaybackConfiguration> apc =
+ mAudioService.getActivePlaybackConfigurations().stream().filter(
+ conf -> conf.getSessionId() == sessionId
+ && conf.getClientPid() == pid).findFirst();
+ if (apc.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "No APCs found when adding loudness codec info. Using AudioAttributes"
+ + " routing for initial update");
+ }
+ updateBundle.putPersistableBundle(Integer.toString(mediaCodecHash),
+ getLoudnessParams(info));
+ } else {
+ final AudioDeviceInfo deviceInfo = apc.get().getAudioDeviceInfo();
+ if (deviceInfo != null) {
+ synchronized (mLock) {
+ // found a piid that matches the configuration
+ piids.add(apc.get().getPlayerInterfaceId());
+
+ updateBundle.putPersistableBundle(
+ Integer.toString(mediaCodecHash),
+ getCodecBundle_l(deviceInfo.getInternalType(),
+ deviceInfo.getAddress(), info));
+ }
+ }
+ }
+ if (!updateBundle.isDefinitelyEmpty()) {
+ dispatchNewLoudnessParameters(sessionId, updateBundle);
+ }
}
}
- void removeLoudnessCodecInfo(int piid, LoudnessCodecInfo codecInfo) {
+ void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) {
if (DEBUG) {
- Log.d(TAG, "removeLoudnessCodecInfo: piid " + piid + " info " + codecInfo);
+ Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo);
}
+
+ int pid = Binder.getCallingPid();
+ final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid);
synchronized (mLock) {
- if (!mStartedPiids.contains(piid)) {
- Log.w(TAG, "Cannot remove loudness info for stopped piid " + piid);
+ if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey(
+ config)) {
+ Log.w(TAG, "Cannot remove loudness info for stopped config " + config);
return;
}
- final Set<LoudnessCodecInfo> infoSet = mStartedPiids.get(piid);
- infoSet.remove(codecInfo);
+ final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
+ if (!codecInfos.remove(codecInfo)) {
+ Log.w(TAG, "Could not find to remove codecInfo " + codecInfo);
+ }
}
}
- PersistableBundle getLoudnessParams(int piid, LoudnessCodecInfo codecInfo) {
+ PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) {
if (DEBUG) {
- Log.d(TAG, "getLoudnessParams: piid " + piid + " codecInfo " + codecInfo);
- }
- try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
- final List<AudioPlaybackConfiguration> configs =
- mAudioService.getActivePlaybackConfigurations();
-
- for (final AudioPlaybackConfiguration apc : configs) {
- if (apc.getPlayerInterfaceId() == piid) {
- final AudioDeviceInfo info = apc.getAudioDeviceInfo();
- if (info == null) {
- Log.i(TAG, "Player with piid " + piid + " is not assigned any device");
- break;
- }
- synchronized (mLock) {
- return getCodecBundle_l(info, codecInfo);
- }
- }
+ Log.d(TAG, "getLoudnessParams: codecInfo " + codecInfo);
+ }
+ final ArrayList<AudioDeviceAttributes> devicesForAttributes =
+ mAudioService.getDevicesForAttributesInt(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .build(), /*forVolume=*/false);
+ if (!devicesForAttributes.isEmpty()) {
+ final AudioDeviceAttributes audioDeviceAttribute = devicesForAttributes.get(0);
+ synchronized (mLock) {
+ return getCodecBundle_l(audioDeviceAttribute.getInternalType(),
+ audioDeviceAttribute.getAddress(), codecInfo);
}
}
@@ -444,13 +541,21 @@ public class LoudnessCodecHelper {
continue;
}
mPiidToDeviceIdCache.put(piid, deviceInfo.getId());
- if (mStartedPiids.contains(piid)) {
+ final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
+ apc.getClientPid());
+ if (mStartedConfigInfo.containsKey(config) && mStartedConfigPiids.containsKey(
+ config)) {
+ if (DEBUG) {
+ Log.d(TAG, "Updating config: " + config + " with APC " + apc);
+ }
updateApcList.add(apc);
+ // update the started piid set
+ mStartedConfigPiids.get(config).add(piid);
}
}
}
- updateApcList.forEach(apc -> updateCodecParametersForConfiguration(apc));
+ updateApcList.forEach(this::updateCodecParametersForConfiguration);
}
/** Updates and dispatches the new loudness parameters for all its registered codecs. */
@@ -458,13 +563,18 @@ public class LoudnessCodecHelper {
// Registered clients
pw.println("\nRegistered clients:\n");
synchronized (mLock) {
- for (int i = 0; i < mStartedPiids.size(); ++i) {
- int piid = mStartedPiids.keyAt(i);
- int pid = mPiidToPidCache.get(piid, -1);
- final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
- pw.println(String.format("Player piid %d pid %d active codec types %s\n", piid,
- pid, codecInfos.stream().map(Object::toString).collect(
- Collectors.joining(", "))));
+ for (Map.Entry<LoudnessTrackId, Set<Integer>> entry : mStartedConfigPiids.entrySet()) {
+ for (Integer piid : entry.getValue()) {
+ int pid = mPiidToPidCache.get(piid, -1);
+ final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(
+ entry.getKey());
+ if (codecInfos != null) {
+ pw.println(
+ String.format("Player piid %d pid %d active codec types %s\n", piid,
+ pid, codecInfos.stream().map(Object::toString).collect(
+ Collectors.joining(", "))));
+ }
+ }
}
pw.println();
}
@@ -481,10 +591,12 @@ public class LoudnessCodecHelper {
if (DEBUG) {
Log.d(TAG, "Removing piid " + piid);
}
- mStartedPiids.delete(piid);
mPiidToDeviceIdCache.delete(piid);
}
}
+
+ mStartedConfigPiids.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
+ mStartedConfigInfo.entrySet().removeIf(entry -> entry.getKey().mPid == pid);
}
}
@@ -499,46 +611,55 @@ public class LoudnessCodecHelper {
}
final PersistableBundle allBundles = new PersistableBundle();
- final int piid = apc.getPlayerInterfaceId();
synchronized (mLock) {
- final Set<LoudnessCodecInfo> codecInfos = mStartedPiids.get(piid);
-
+ final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(),
+ apc.getClientPid());
+ final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config);
final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo();
+
if (codecInfos != null && deviceInfo != null) {
for (LoudnessCodecInfo info : codecInfos) {
- allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
- getCodecBundle_l(deviceInfo, info));
+ if (info != null) {
+ allBundles.putPersistableBundle(Integer.toString(info.hashCode()),
+ getCodecBundle_l(deviceInfo.getInternalType(),
+ deviceInfo.getAddress(), info));
+ }
}
}
}
if (!allBundles.isDefinitelyEmpty()) {
- dispatchNewLoudnessParameters(piid, allBundles);
+ dispatchNewLoudnessParameters(apc.getSessionId(), allBundles);
}
}
- private void dispatchNewLoudnessParameters(int piid, PersistableBundle bundle) {
+ private void dispatchNewLoudnessParameters(int sessionId,
+ PersistableBundle bundle) {
if (DEBUG) {
- Log.d(TAG, "dispatchNewLoudnessParameters: piid " + piid + " bundle: " + bundle);
+ Log.d(TAG,
+ "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle);
}
final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; ++i) {
try {
mLoudnessUpdateDispatchers.getBroadcastItem(i)
- .dispatchLoudnessCodecParameterChange(piid, bundle);
+ .dispatchLoudnessCodecParameterChange(sessionId, bundle);
} catch (RemoteException e) {
- Log.e(TAG, "Error dispatching for piid: " + piid + " bundle: " + bundle , e);
+ Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
+ e);
}
}
mLoudnessUpdateDispatchers.finishBroadcast();
}
@GuardedBy("mLock")
- private PersistableBundle getCodecBundle_l(AudioDeviceInfo deviceInfo,
- LoudnessCodecInfo codecInfo) {
+ private PersistableBundle getCodecBundle_l(int internalDeviceType,
+ String address,
+ LoudnessCodecInfo codecInfo) {
LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder();
- LoudnessCodecInputProperties prop = builder.setDeviceSplRange(getDeviceSplRange(deviceInfo))
+ LoudnessCodecInputProperties prop = builder.setDeviceSplRange(
+ getDeviceSplRange(internalDeviceType, address))
.setIsDownmixing(codecInfo.isDownmixing)
.setMetadataType(codecInfo.metadataType)
.build();
@@ -552,14 +673,15 @@ public class LoudnessCodecHelper {
}
@DeviceSplRange
- private int getDeviceSplRange(AudioDeviceInfo deviceInfo) {
- final int internalDeviceType = deviceInfo.getInternalType();
- final @AudioDeviceCategory int deviceCategory;
- if (automaticBtDeviceType()) {
- deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress());
- } else {
- deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
- deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType));
+ private int getDeviceSplRange(int internalDeviceType, String address) {
+ @AudioDeviceCategory int deviceCategory;
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ if (automaticBtDeviceType()) {
+ deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(address);
+ } else {
+ deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
+ address, AudioSystem.isBluetoothLeDevice(internalDeviceType));
+ }
}
if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) {
final String splRange = SystemProperties.get(
@@ -595,20 +717,28 @@ public class LoudnessCodecHelper {
private static String splRangeToString(@DeviceSplRange int splRange) {
switch (splRange) {
- case SPL_RANGE_LARGE: return "large";
- case SPL_RANGE_MEDIUM: return "medium";
- case SPL_RANGE_SMALL: return "small";
- default: return "unknown";
+ case SPL_RANGE_LARGE:
+ return "large";
+ case SPL_RANGE_MEDIUM:
+ return "medium";
+ case SPL_RANGE_SMALL:
+ return "small";
+ default:
+ return "unknown";
}
}
@DeviceSplRange
private static int stringToSplRange(String splRange) {
switch (splRange) {
- case "large": return SPL_RANGE_LARGE;
- case "medium": return SPL_RANGE_MEDIUM;
- case "small": return SPL_RANGE_SMALL;
- default: return SPL_RANGE_UNKNOWN;
+ case "large":
+ return SPL_RANGE_LARGE;
+ case "medium":
+ return SPL_RANGE_MEDIUM;
+ case "small":
+ return SPL_RANGE_SMALL;
+ default:
+ return SPL_RANGE_UNKNOWN;
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index f8a98675c0c7..7f04628ff6de 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -87,10 +87,30 @@ public interface BiometricContext {
*
* @param context context that will be modified when changed
* @param consumer callback when the context is modified
+ *
+ * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer,
+ * Consumer, AuthenticateOptions)}
+ * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed.
*/
+ @Deprecated
void subscribe(@NonNull OperationContextExt context,
@NonNull Consumer<OperationContext> consumer);
+ /**
+ * Subscribe to context changes and start the HAL operation.
+ *
+ * Note that this method only notifies for properties that are visible to the HAL.
+ *
+ * @param context context that will be modified when changed
+ * @param startHalConsumer callback to start HAL operation after subscription is done
+ * @param updateContextConsumer callback when the context is modified
+ * @param options authentication options for updating the context
+ */
+ void subscribe(@NonNull OperationContextExt context,
+ @NonNull Consumer<OperationContext> startHalConsumer,
+ @NonNull Consumer<OperationContext> updateContextConsumer,
+ @Nullable AuthenticateOptions options);
+
/** Unsubscribe from context changes. */
void unsubscribe(@NonNull OperationContextExt context);
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index 535b7b743625..d8dfa60bdb51 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -238,6 +238,19 @@ public final class BiometricContextProvider implements BiometricContext {
}
@Override
+ public void subscribe(@NonNull OperationContextExt context,
+ @NonNull Consumer<OperationContext> startHalConsumer,
+ @NonNull Consumer<OperationContext> updateContextConsumer,
+ @Nullable AuthenticateOptions options) {
+ mSubscribers.put(updateContext(context, context.isCrypto()), updateContextConsumer);
+ if (options != null) {
+ startHalConsumer.accept(context.toAidlContext(options));
+ } else {
+ startHalConsumer.accept(context.toAidlContext());
+ }
+ }
+
+ @Override
public void unsubscribe(@NonNull OperationContextExt context) {
mSubscribers.remove(context);
}
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index 0045d44af9a1..da4e5154d49e 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -102,6 +102,24 @@ public class OperationContextExt {
* @return the underlying AIDL context
*/
@NonNull
+ public OperationContext toAidlContext(@NonNull AuthenticateOptions options) {
+ if (options instanceof FaceAuthenticateOptions) {
+ return toAidlContext((FaceAuthenticateOptions) options);
+ }
+ if (options instanceof FingerprintAuthenticateOptions) {
+ return toAidlContext((FingerprintAuthenticateOptions) options);
+ }
+ throw new IllegalStateException("Authenticate options are invalid.");
+ }
+
+ /**
+ * Gets the subset of the context that can be shared with the HAL and updates
+ * it with the given options.
+ *
+ * @param options authenticate options
+ * @return the underlying AIDL context
+ */
+ @NonNull
public OperationContext toAidlContext(@NonNull FaceAuthenticateOptions options) {
mAidlContext.authenticateReason = AuthenticateReason
.faceAuthenticateReason(getAuthReason(options));
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 0f01510bd908..b0649b90c466 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.os.IBinder;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -93,6 +94,9 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor {
}
protected OperationContextExt getOperationContext() {
+ if (Flags.deHidl()) {
+ return mOperationContext;
+ }
return getBiometricContext().updateContext(mOperationContext, isCryptoOperation());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 470dc4b7172c..22e399c6747b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -37,6 +37,7 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -153,7 +154,11 @@ public class FaceAuthenticationClient
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
} else {
- mCancellationSignal = doAuthenticate();
+ if (Flags.deHidl()) {
+ startAuthenticate();
+ } else {
+ mCancellationSignal = doAuthenticate();
+ }
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -182,6 +187,33 @@ public class FaceAuthenticationClient
}
}
+ private void startAuthenticate() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ final OperationContextExt opContext = getOperationContext();
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ mCancellationSignal = session.getSession().authenticateWithContext(
+ mOperationId, ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting auth", e);
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }, getOptions());
+ } else {
+ mCancellationSignal = session.getSession().authenticate(mOperationId);
+ }
+ }
+
@Override
protected void stopHalOperation() {
unsubscribeBiometricContext();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index a529fb9779a7..5ddddda4e201 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -29,6 +29,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -111,7 +112,11 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements
}
try {
- mCancellationSignal = doDetectInteraction();
+ if (Flags.deHidl()) {
+ startDetect();
+ } else {
+ mCancellationSignal = doDetectInteraction();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting face detect", e);
mCallback.onClientFinished(this, false /* success */);
@@ -138,6 +143,30 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements
}
}
+ private void startDetect() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ final OperationContextExt opContext = getOperationContext();
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ mCancellationSignal = session.getSession().detectInteractionWithContext(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting face detect", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }, mOptions);
+ } else {
+ mCancellationSignal = session.getSession().detectInteraction();
+ }
+ }
+
@Override
public void onInteractionDetected() {
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 0af6e40434ef..f5c452992674 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -36,6 +36,7 @@ import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -187,7 +188,11 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
features[i] = featureList.get(i);
}
- mCancellationSignal = doEnroll(features);
+ if (Flags.deHidl()) {
+ startEnroll(features);
+ } else {
+ mCancellationSignal = doEnroll(features);
+ }
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Exception when requesting enroll", e);
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -231,6 +236,48 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
}
}
+ private void startEnroll(byte[] features) throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+ final HardwareAuthToken hat =
+ HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+ if (session.hasContextMethods()) {
+ final OperationContextExt opContext = getOperationContext();
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ if (session.supportsFaceEnrollOptions()) {
+ FaceEnrollOptions options = new FaceEnrollOptions();
+ options.hardwareAuthToken = hat;
+ options.enrollmentType = EnrollmentType.DEFAULT;
+ options.features = features;
+ options.nativeHandlePreview = null;
+ options.context = ctx;
+ options.surfacePreview = mPreviewSurface;
+ mCancellationSignal = session.getSession().enrollWithOptions(options);
+ } else {
+ mCancellationSignal = session.getSession().enrollWithContext(
+ hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, ctx);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception when requesting enroll", e);
+ onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }, null /* options */);
+ } else {
+ mCancellationSignal = session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
+ mHwPreviewHandle);
+ }
+ }
+
+
@Override
protected void stopHalOperation() {
unsubscribeBiometricContext();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 145885de5c32..f7e812330ece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -44,6 +44,7 @@ import android.provider.Settings;
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -297,7 +298,11 @@ public class FingerprintAuthenticationClient
}
try {
- mCancellationSignal = doAuthenticate();
+ if (Flags.deHidl()) {
+ startAuthentication();
+ } else {
+ mCancellationSignal = doAuthenticate();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(
@@ -353,6 +358,51 @@ public class FingerprintAuthenticationClient
return cancel;
}
+ private void startAuthentication() {
+ final AidlSession session = getFreshDaemon();
+ final OperationContextExt opContext = getOperationContext();
+
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ if (session.hasContextMethods()) {
+ mCancellationSignal = session.getSession().authenticateWithContext(mOperationId,
+ ctx);
+ } else {
+ mCancellationSignal = session.getSession().authenticate(mOperationId);
+ }
+
+ if (getBiometricContext().isAwake()) {
+ mALSProbeCallback.getProbe().enable();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(
+ BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mSensorOverlays.hide(getSensorId());
+ if (sidefpsControllerRefactor()) {
+ mAuthenticationStateListeners.onAuthenticationStopped();
+ }
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ if (session.hasContextMethods()) {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }
+
+ final boolean isAwake = getBiometricContext().isAwake();
+ if (isAwake) {
+ mALSProbeCallback.getProbe().enable();
+ } else {
+ mALSProbeCallback.getProbe().disable();
+ }
+ }, getOptions());
+ }
+
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 3aab7b300a87..a7fb7741fee1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -105,7 +106,11 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
this);
try {
- mCancellationSignal = doDetectInteraction();
+ if (Flags.deHidl()) {
+ startDetectInteraction();
+ } else {
+ mCancellationSignal = doDetectInteraction();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
mSensorOverlays.hide(getSensorId());
@@ -139,6 +144,32 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession>
}
}
+ private void startDetectInteraction() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+
+ if (session.hasContextMethods()) {
+ final OperationContextExt opContext = getOperationContext();
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ mCancellationSignal = session.getSession().detectInteractionWithContext(
+ ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to start detect interaction", e);
+ mSensorOverlays.hide(getSensorId());
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }, mOptions);
+ } else {
+ mCancellationSignal = session.getSession().detectInteraction();
+ }
+ }
+
@Override
public void onInteractionDetected() {
vibrateSuccess();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index bf5011de1e59..3fb9223249b4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -39,6 +39,7 @@ import android.os.RemoteException;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -200,7 +201,11 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
- mCancellationSignal = doEnroll();
+ if (Flags.deHidl()) {
+ startEnroll();
+ } else {
+ mCancellationSignal = doEnroll();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -237,6 +242,35 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
}
}
+ private void startEnroll() throws RemoteException {
+ final AidlSession session = getFreshDaemon();
+ final HardwareAuthToken hat =
+ HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
+
+ if (session.hasContextMethods()) {
+ final OperationContextExt opContext = getOperationContext();
+ getBiometricContext().subscribe(opContext, ctx -> {
+ try {
+ mCancellationSignal = session.getSession().enrollWithContext(
+ hat, ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting enroll", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }, ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ }, null /* options */);
+ } else {
+ mCancellationSignal = session.getSession().enroll(hat);
+ }
+ }
+
@Override
protected void stopHalOperation() {
mSensorOverlays.hide(getSensorId());
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fb4943a9f4ca..67c23fc4db12 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1328,8 +1328,7 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconDisplayContext = null;
}
- updateAdditionalDisplayInputProperties(displayId,
- AdditionalDisplayInputProperties::reset);
+ updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
// TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
// removed in InputDispatcher instead of this callback.
@@ -1812,8 +1811,6 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconType = icon.getType();
mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
- if (!mCurrentDisplayProperties.pointerIconVisible) return false;
-
return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
}
}
@@ -3478,6 +3475,10 @@ public class InputManagerService extends IInputManager.Stub
private void applyAdditionalDisplayInputPropertiesLocked(
AdditionalDisplayInputProperties properties) {
// Handle changes to each of the individual properties.
+ // TODO(b/293587049): This approach for updating pointer display properties is only for when
+ // PointerChoreographer is disabled. Remove this logic when PointerChoreographer is
+ // permanently enabled.
+
if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) {
mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible;
if (properties.pointerIconVisible) {
@@ -3496,7 +3497,6 @@ public class InputManagerService extends IInputManager.Stub
!= mCurrentDisplayProperties.mousePointerAccelerationEnabled) {
mCurrentDisplayProperties.mousePointerAccelerationEnabled =
properties.mousePointerAccelerationEnabled;
- mNative.setMousePointerAccelerationEnabled(properties.mousePointerAccelerationEnabled);
}
}
@@ -3509,7 +3509,16 @@ public class InputManagerService extends IInputManager.Stub
properties = new AdditionalDisplayInputProperties();
mAdditionalDisplayInputProperties.put(displayId, properties);
}
+ final boolean oldPointerIconVisible = properties.pointerIconVisible;
+ final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled;
updater.accept(properties);
+ if (oldPointerIconVisible != properties.pointerIconVisible) {
+ mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
+ }
+ if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) {
+ mNative.setMousePointerAccelerationEnabled(displayId,
+ properties.mousePointerAccelerationEnabled);
+ }
if (properties.allDefaults()) {
mAdditionalDisplayInputProperties.remove(displayId);
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index c3ef80f3624a..6f5202029998 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -119,7 +119,7 @@ interface NativeInputManagerService {
void setPointerSpeed(int speed);
- void setMousePointerAccelerationEnabled(boolean enabled);
+ void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
void setTouchpadPointerSpeed(int speed);
@@ -190,6 +190,8 @@ interface NativeInputManagerService {
boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId,
@NonNull IBinder inputToken);
+ void setPointerIconVisibility(int displayId, boolean visible);
+
void requestPointerCapture(IBinder windowToken, boolean enabled);
boolean canDispatchToDisplay(int deviceId, int displayId);
@@ -352,7 +354,7 @@ interface NativeInputManagerService {
public native void setPointerSpeed(int speed);
@Override
- public native void setMousePointerAccelerationEnabled(boolean enabled);
+ public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
public native void setTouchpadPointerSpeed(int speed);
@@ -452,6 +454,9 @@ interface NativeInputManagerService {
int pointerId, IBinder inputToken);
@Override
+ public native void setPointerIconVisibility(int displayId, boolean visible);
+
+ @Override
public native void requestPointerCapture(IBinder windowToken, boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 9c4225dc2542..39df5be0f2fa 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1384,7 +1384,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
try {
reportLocation(LocationResult.wrap(location).validate());
} catch (BadLocationException e) {
- throw new IllegalArgumentException(e);
+ Log.e(TAG, "Dropping invalid location: " + e);
+ return;
}
if (mStarted) {
@@ -1759,7 +1760,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
try {
reportLocation(LocationResult.wrap(locations).validate());
} catch (BadLocationException e) {
- throw new IllegalArgumentException(e);
+ Log.e(TAG, "Dropping invalid locations: " + e);
+ return;
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 28a1c7ad0540..85a131579497 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2336,6 +2336,11 @@ class MediaRouter2ServiceImpl {
mSystemProvider.getDefaultRoute());
}
+ private static String getPackageNameFromNullableRecord(
+ @Nullable RouterRecord routerRecord) {
+ return routerRecord != null ? routerRecord.mPackageName : "<null router record>";
+ }
+
private static String toLoggingMessage(
String source, String providerId, ArrayList<MediaRoute2Info> routes) {
String routesString =
@@ -2573,8 +2578,17 @@ class MediaRouter2ServiceImpl {
RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
- Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
- + "packageName=" + routerRecord.mPackageName + " route=" + route);
+ Slog.w(
+ TAG,
+ "Ignoring "
+ + description
+ + " route from non-matching router."
+ + " routerRecordPackageName="
+ + getPackageNameFromNullableRecord(routerRecord)
+ + " matchingRecordPackageName="
+ + getPackageNameFromNullableRecord(matchingRecord)
+ + " route="
+ + route);
return false;
}
@@ -2613,9 +2627,15 @@ class MediaRouter2ServiceImpl {
@Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId) {
final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
- Slog.w(TAG, "Ignoring releasing session from non-matching router. packageName="
- + (routerRecord == null ? null : routerRecord.mPackageName)
- + " uniqueSessionId=" + uniqueSessionId);
+ Slog.w(
+ TAG,
+ "Ignoring releasing session from non-matching router."
+ + " routerRecordPackageName="
+ + getPackageNameFromNullableRecord(routerRecord)
+ + " matchingRecordPackageName="
+ + getPackageNameFromNullableRecord(matchingRecord)
+ + " uniqueSessionId="
+ + uniqueSessionId);
return;
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index c110fb67b54f..200b17bc2f97 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -16,7 +16,12 @@
package com.android.server.pm;
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.Flags;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
@@ -27,6 +32,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
@@ -69,26 +75,29 @@ public class BackgroundInstallControlService extends SystemService {
private static final String DISK_FILE_NAME = "states";
private static final String DISK_DIR_NAME = "bic";
- private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
+ private static final String ENFORCE_PERMISSION_ERROR_MSG =
+ "User is not permitted to call service: ";
+ private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
private static final int MSG_USAGE_EVENT_RECEIVED = 0;
private static final int MSG_PACKAGE_ADDED = 1;
private static final int MSG_PACKAGE_REMOVED = 2;
private final BinderService mBinderService;
private final PackageManager mPackageManager;
+ // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible.
+ // b/310983905
private final PackageManagerInternal mPackageManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final Handler mHandler;
private final File mDiskFile;
-
+ private final Context mContext;
private SparseSetArray<String> mBackgroundInstalledPackages = null;
// User ID -> package name -> set of foreground time frame
- private final SparseArrayMap<String,
- TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
- new SparseArrayMap<>();
+ private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
+ mInstallerForegroundTimeFrames = new SparseArrayMap<>();
public BackgroundInstallControlService(@NonNull Context context) {
this(new InjectorImpl(context));
@@ -102,15 +111,13 @@ public class BackgroundInstallControlService extends SystemService {
mPermissionManager = injector.getPermissionManager();
mHandler = new EventHandler(injector.getLooper(), this);
mDiskFile = injector.getDiskFile();
+ mContext = injector.getContext();
UsageStatsManagerInternal usageStatsManagerInternal =
injector.getUsageStatsManagerInternal();
usageStatsManagerInternal.registerListener(
(userId, event) ->
- mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
- userId,
- 0,
- event).sendToTarget()
- );
+ mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event)
+ .sendToTarget());
mBinderService = new BinderService(this);
}
@@ -124,12 +131,17 @@ public class BackgroundInstallControlService extends SystemService {
@Override
public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ if (Flags.bicClient()) {
+ mService.enforceCallerPermissions();
+ }
if (!Build.IS_DEBUGGABLE) {
return mService.getBackgroundInstalledPackages(flags, userId);
}
// The debug.transparency.bg-install-apps (only works for debuggable builds)
// is used to set mock list of background installed apps for testing.
// The list of apps' names is delimited by ",".
+ // TODO: Remove after migrating test to new background install method using
+ // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905
String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
if (TextUtils.isEmpty(propertyString)) {
return mService.getBackgroundInstalledPackages(flags, userId);
@@ -137,25 +149,36 @@ public class BackgroundInstallControlService extends SystemService {
return mService.getMockBackgroundInstalledPackages(propertyString);
}
}
+
+ }
+
+ @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+ void enforceCallerPermissions() throws SecurityException {
+ mContext.enforceCallingOrSelfPermission(GET_BACKGROUND_INSTALLED_PACKAGES,
+ ENFORCE_PERMISSION_ERROR_MSG + GET_BACKGROUND_INSTALLED_PACKAGES);
}
@VisibleForTesting
ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+ final long token = Binder.clearCallingIdentity();
+ try {
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
PackageManager.PackageInfoFlags.of(flags), userId);
- initBackgroundInstalledPackages();
-
- ListIterator<PackageInfo> iter = packages.listIterator();
- while (iter.hasNext()) {
- String packageName = iter.next().packageName;
- if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
- iter.remove();
+ initBackgroundInstalledPackages();
+ ListIterator<PackageInfo> iter = packages.listIterator();
+ while (iter.hasNext()) {
+ String packageName = iter.next().packageName;
+ if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
+ iter.remove();
+ }
}
- }
- return new ParceledListSlice<>(packages);
+ return new ParceledListSlice<>(packages);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -168,8 +191,9 @@ public class BackgroundInstallControlService extends SystemService {
List<PackageInfo> mockPackages = new ArrayList<>();
for (String name : mockPackageNames) {
try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
+ PackageInfo packageInfo =
+ mPackageManager.getPackageInfo(
+ name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
mockPackages.add(packageInfo);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package's PackageInfo not found " + name);
@@ -190,18 +214,16 @@ public class BackgroundInstallControlService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_USAGE_EVENT_RECEIVED: {
- mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
+ case MSG_USAGE_EVENT_RECEIVED:
+ mService.handleUsageEvent(
+ (UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
break;
- }
- case MSG_PACKAGE_ADDED: {
+ case MSG_PACKAGE_ADDED:
mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
break;
- }
- case MSG_PACKAGE_REMOVED: {
+ case MSG_PACKAGE_REMOVED:
mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
break;
- }
default:
Slog.w(TAG, "Unknown message: " + msg.what);
}
@@ -211,8 +233,9 @@ public class BackgroundInstallControlService extends SystemService {
void handlePackageAdd(String packageName, int userId) {
ApplicationInfo appInfo = null;
try {
- appInfo = mPackageManager.getApplicationInfoAsUser(packageName,
- PackageManager.ApplicationInfoFlags.of(0), userId);
+ appInfo =
+ mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.ApplicationInfoFlags.of(0), userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package's appInfo not found " + packageName);
return;
@@ -231,15 +254,18 @@ public class BackgroundInstallControlService extends SystemService {
// the installers without INSTALL_PACKAGES perm can't perform
// the installation in background. So we can just filter out them.
- if (mPermissionManager.checkPermission(installerPackageName,
- android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
- userId) != PackageManager.PERMISSION_GRANTED) {
+ if (mPermissionManager.checkPermission(
+ installerPackageName,
+ android.Manifest.permission.INSTALL_PACKAGES,
+ Context.DEVICE_ID_DEFAULT,
+ userId)
+ != PERMISSION_GRANTED) {
return;
}
// convert up-time to current time.
- final long installTimestamp = System.currentTimeMillis()
- - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+ final long installTimestamp =
+ System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
if (installedByAdb(initiatingPackageName)
|| wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -257,8 +283,8 @@ public class BackgroundInstallControlService extends SystemService {
return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
}
- private boolean wasForegroundInstallation(String installerPackageName,
- int userId, long installTimestamp) {
+ private boolean wasForegroundInstallation(
+ String installerPackageName, int userId, long installTimestamp) {
TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
mInstallerForegroundTimeFrames.get(userId, installerPackageName);
@@ -347,12 +373,12 @@ public class BackgroundInstallControlService extends SystemService {
for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
int userId = mBackgroundInstalledPackages.keyAt(i);
for (String packageName : mBackgroundInstalledPackages.get(userId)) {
- long token = protoOutputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ long token =
+ protoOutputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
protoOutputStream.write(
BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.USER_ID, userId + 1);
+ protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1);
protoOutputStream.end(token);
}
}
@@ -385,23 +411,28 @@ public class BackgroundInstallControlService extends SystemService {
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
continue;
}
- long token = protoInputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ long token =
+ protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
String packageName = null;
int userId = UserHandle.USER_NULL;
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (protoInputStream.getFieldNumber()) {
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
- packageName = protoInputStream.readString(
- BackgroundInstalledPackageProto.PACKAGE_NAME);
+ packageName =
+ protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
break;
case (int) BackgroundInstalledPackageProto.USER_ID:
- userId = protoInputStream.readInt(
- BackgroundInstalledPackageProto.USER_ID) - 1;
+ userId =
+ protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID)
+ - 1;
break;
default:
- Slog.w(TAG, "Undefined field in proto: "
- + protoInputStream.getFieldNumber());
+ Slog.w(
+ TAG,
+ "Undefined field in proto: "
+ + protoInputStream.getFieldNumber());
}
}
protoInputStream.end(token);
@@ -430,9 +461,12 @@ public class BackgroundInstallControlService extends SystemService {
if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
return true;
}
- return mPermissionManager.checkPermission(pkgName,
- android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT,
- userId) == PackageManager.PERMISSION_GRANTED;
+ return mPermissionManager.checkPermission(
+ pkgName,
+ android.Manifest.permission.INSTALL_PACKAGES,
+ Context.DEVICE_ID_DEFAULT,
+ userId)
+ == PERMISSION_GRANTED;
}
@Override
@@ -446,21 +480,22 @@ public class BackgroundInstallControlService extends SystemService {
publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
}
- mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_ADDED,
- userId, 0, packageName).sendToTarget();
- }
+ mPackageManagerInternal.getPackageList(
+ new PackageManagerInternal.PackageListObserver() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
+ .sendToTarget();
+ }
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
- userId, 0, packageName).sendToTarget();
- }
- });
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
+ .sendToTarget();
+ }
+ });
}
// The foreground time frame (ForegroundTimeFrame) represents the period
@@ -516,7 +551,7 @@ public class BackgroundInstallControlService extends SystemService {
}
/**
- * Dependency injector for {@link #BackgroundInstallControlService)}.
+ * Dependency injector for {@link BackgroundInstallControlService}.
*/
interface Injector {
Context getContext();
@@ -532,6 +567,7 @@ public class BackgroundInstallControlService extends SystemService {
Looper getLooper();
File getDiskFile();
+
}
private static final class InjectorImpl implements Injector {
@@ -568,11 +604,11 @@ public class BackgroundInstallControlService extends SystemService {
@Override
public Looper getLooper() {
- ServiceThread serviceThread = new ServiceThread(TAG,
- android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ ServiceThread serviceThread =
+ new ServiceThread(
+ TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
serviceThread.start();
return serviceThread.getLooper();
-
}
@Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ac826afc1d22..68c95b16d318 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,10 @@ package com.android.server.pm;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
+import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -43,6 +47,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyCache;
@@ -213,6 +218,7 @@ public class LauncherAppsService extends SystemService {
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageManagerInternal mPackageManagerInternal;
+ private final AppOpsManager mAppOpsManager;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
@@ -253,6 +259,7 @@ public class LauncherAppsService extends SystemService {
LocalServices.getService(ShortcutServiceInternal.class));
mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mShortcutServiceInternal.addListener(mPackageMonitor);
mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
@@ -1998,6 +2005,23 @@ public class LauncherAppsService extends SystemService {
}
}
+ @Override
+ public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
+ boolean enableUnarchivalConfirmation) {
+ int callingUid = Binder.getCallingUid();
+ Binder.withCleanCallingIdentity(
+ () -> {
+ mAppOpsManager.setUidMode(
+ OP_ARCHIVE_ICON_OVERLAY,
+ callingUid,
+ enableIconOverlay ? MODE_ALLOWED : MODE_IGNORED);
+ mAppOpsManager.setUidMode(
+ OP_UNARCHIVAL_CONFIRMATION,
+ callingUid,
+ enableUnarchivalConfirmation ? MODE_ALLOWED : MODE_IGNORED);
+ });
+ }
+
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 09a91eda483a..c1b3673865dc 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_ABORTED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
@@ -31,6 +32,7 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
+import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -66,8 +68,11 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Binder;
import android.os.Bundle;
@@ -269,11 +274,12 @@ public class PackageArchiver {
Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
try {
- // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
- false /* showUnarchivalConfirmation= */);
+ getAppOpsManager().checkOp(
+ AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
+ == MODE_ALLOWED);
} catch (Throwable t) {
Slog.e(TAG, TextUtils.formatSimple(
"Unexpected error occurred while unarchiving package %s: %s.", packageName,
@@ -379,9 +385,8 @@ public class PackageArchiver {
verifyNotSystemApp(ps.getFlags());
verifyInstalled(ps, userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
- verifyInstaller(responsibleInstallerPackage, userId);
- ApplicationInfo installerInfo = snapshot.getApplicationInfo(
- responsibleInstallerPackage, /* flags= */ 0, userId);
+ ApplicationInfo installerInfo = verifyInstaller(
+ snapshot, responsibleInstallerPackage, userId);
verifyOptOutStatus(packageName,
UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId())));
@@ -421,10 +426,10 @@ public class PackageArchiver {
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
for (int i = 0, size = mainActivities.size(); i < size; ++i) {
var mainActivity = mainActivities.get(i);
- Path iconPath = storeDrawable(
- packageName, mainActivity.getIcon(), userId, i, iconSize);
- Path monochromePath = storeDrawable(
- packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize);
+ Path iconPath = storeAdaptiveDrawable(
+ packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize);
+ Path monochromePath = storeAdaptiveDrawable(
+ packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize);
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
mainActivity.getLabel().toString(),
@@ -451,7 +456,8 @@ public class PackageArchiver {
List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
- Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
+ Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize);
+ // i * 2 + 1 reserved for monochromeIcon
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
mainActivity.getLabel().toString(),
@@ -495,8 +501,30 @@ public class PackageArchiver {
return iconFile.toPath();
}
- private void verifyInstaller(String installerPackageName, int userId)
- throws PackageManager.NameNotFoundException {
+ /**
+ * Create an <a
+ * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
+ * adaptive icon</a> from an icon.
+ * This is necessary so the icon can be displayed properly by different launchers.
+ */
+ private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable,
+ @UserIdInt int userId, int index, int iconSize) throws IOException {
+ if (iconDrawable == null) {
+ return null;
+ }
+
+ // see BaseIconFactory#createShapedIconBitmap
+ float inset = getExtraInsetFraction();
+ inset = inset / (1 + 2 * inset);
+ Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
+ new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset));
+
+ return storeDrawable(packageName, d, userId, index, iconSize);
+ }
+
+
+ private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName,
+ int userId) throws PackageManager.NameNotFoundException {
if (TextUtils.isEmpty(installerPackageName)) {
throw new PackageManager.NameNotFoundException("No installer found");
}
@@ -505,6 +533,12 @@ public class PackageArchiver {
&& !verifySupportsUnarchival(installerPackageName, userId)) {
throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
}
+ ApplicationInfo appInfo = snapshot.getApplicationInfo(
+ installerPackageName, /* flags=*/ 0, userId);
+ if (appInfo == null) {
+ throw new PackageManager.NameNotFoundException("Failed to obtain Installer info");
+ }
+ return appInfo;
}
/**
@@ -570,7 +604,7 @@ public class PackageArchiver {
}
try {
- verifyInstaller(getResponsibleInstallerPackage(ps), userId);
+ verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId);
getLauncherActivityInfos(packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
return false;
@@ -762,7 +796,8 @@ public class PackageArchiver {
* <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
* launcher activities, only one of the icons is returned arbitrarily.
*/
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ String callingPackageName) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -785,7 +820,13 @@ public class PackageArchiver {
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+ Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
+ if (getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
+ == MODE_ALLOWED) {
+ icon = includeCloudOverlay(icon);
+ }
+ return icon;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7bf9fe7aa7e2..cfafe7cc00df 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -788,6 +788,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ if (Flags.recoverabilityDetection()) {
+ if (params.rollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH
+ || params.rollbackImpactLevel
+ == PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+ if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+ throw new IllegalArgumentException(
+ "Can't set rollbackImpactLevel when rollback is not enabled");
+ }
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Setting rollbackImpactLevel requires the MANAGE_ROLLBACKS permission");
+ }
+ } else if (params.rollbackImpactLevel < 0) {
+ throw new IllegalArgumentException("rollbackImpactLevel can't be negative.");
+ }
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (isApex) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 117d03fd059b..1a0e8079996e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1294,6 +1294,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
info.installFlags = params.installFlags;
info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
+ info.rollbackImpactLevel = params.rollbackImpactLevel;
info.isMultiPackage = params.isMultiPackage;
info.isStaged = params.isStaged;
info.rollbackDataPolicy = params.rollbackDataPolicy;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c5b5a761497d..f09fa21792dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4608,6 +4608,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
});
// Send UNSTOPPED broadcast if necessary
if (wasStopped && Flags.stayStopped()) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "unstoppedBroadcast");
final PackageManagerInternal pmi =
mInjector.getLocalService(PackageManagerInternal.class);
final int [] userIds = resolveUserIds(userId);
@@ -4627,6 +4628,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
broadcastAllowList, mHandler, null /* filterExtras */);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
}
@@ -6386,8 +6388,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Override
- public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
- return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ @NonNull String callingPackageName) {
+ return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user,
+ callingPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ca00c84da724..88dc60c3f70f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -297,6 +297,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runSetHiddenSetting(true);
case "unhide":
return runSetHiddenSetting(false);
+ case "unstop":
+ return runSetStoppedState(false);
case "suspend":
return runSuspend(true, 0);
case "suspend-quarantine":
@@ -2662,6 +2664,26 @@ class PackageManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetStoppedState(boolean state) throws RemoteException {
+ int userId = UserHandle.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ }
+
+ String pkg = getNextArg();
+ if (pkg == null) {
+ getErrPrintWriter().println("Error: no package specified");
+ return 1;
+ }
+ final int translatedUserId =
+ translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState");
+ mInterface.setPackageStoppedState(pkg, state, userId);
+ getOutPrintWriter().println("Package " + pkg + " new stopped state: "
+ + mInterface.isPackageStoppedForUser(pkg, translatedUserId));
+ return 0;
+ }
+
private int runSetDistractingRestriction() {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_SYSTEM;
@@ -4934,6 +4956,8 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println("");
+ pw.println(" unstop [--user USER_ID] PACKAGE");
+ pw.println("");
pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]");
pw.println(" Suspends the specified package(s) (as user).");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c94111c31ef4..a6598d602d01 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -712,19 +712,24 @@ public class UserManagerService extends IUserManager.Stub {
boolean isAutoLockOnDeviceLockSelected =
autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
- int privateProfileUserId = getPrivateProfileUserId();
- if (privateProfileUserId != UserHandle.USER_NULL) {
- Slog.i(LOG_TAG, "Auto-locking private space with user-id "
- + privateProfileUserId);
- setQuietModeEnabledAsync(privateProfileUserId,
- /* enableQuietMode */true, /* target */ null,
- mContext.getPackageName());
- }
+ autoLockPrivateSpace();
}
}
}
@VisibleForTesting
+ void autoLockPrivateSpace() {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+ + privateProfileUserId);
+ setQuietModeEnabledAsync(privateProfileUserId,
+ /* enableQuietMode */true, /* target */ null,
+ mContext.getPackageName());
+ }
+ }
+
+ @VisibleForTesting
void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
IntentSender target, @Nullable String callingPackage) {
if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) {
@@ -1036,9 +1041,18 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ if (isAutoLockingPrivateSpaceOnRestartsEnabled()) {
+ autoLockPrivateSpace();
+ }
+
markEphemeralUsersForRemoval();
}
+ private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+ }
+
/**
* This method retrieves the {@link UserManagerInternal} only for the purpose of
* PackageManagerService construction.
diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
index 3efac81d44e3..d138606369b9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
@@ -26,6 +26,7 @@ import android.util.ArrayMap;
public final class PermissionAllowlist {
@NonNull
private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>();
+
@NonNull
private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
new ArrayMap<>();
@@ -43,6 +44,19 @@ public final class PermissionAllowlist {
mApexPrivilegedAppAllowlists = new ArrayMap<>();
@NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist =
+ new ArrayMap<>();
+ @NonNull
+ private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist =
+ new ArrayMap<>();
+
+ @NonNull
public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() {
return mOemAppAllowlist;
}
@@ -73,6 +87,26 @@ public final class PermissionAllowlist {
return mApexPrivilegedAppAllowlists;
}
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() {
+ return mSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() {
+ return mVendorSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() {
+ return mProductSignatureAppAllowlist;
+ }
+
+ @NonNull
+ public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() {
+ return mSystemExtSignatureAppAllowlist;
+ }
+
@Nullable
public Boolean getOemAppAllowlistState(@NonNull String packageName,
@NonNull String permissionName) {
@@ -137,4 +171,44 @@ public final class PermissionAllowlist {
}
return permissions.get(permissionName);
}
+
+ @Nullable
+ public Boolean getSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
+
+ @Nullable
+ public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName,
+ @NonNull String permissionName) {
+ ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName);
+ if (permissions == null) {
+ return null;
+ }
+ return permissions.get(permissionName);
+ }
}
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 5e8b4de3894e..7808c4ed50a4 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Environment;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -368,6 +369,7 @@ public class RoleServicePlatformHelperImpl implements RoleServicePlatformHelper
dataOutputStream.writeUTF(profileOwner);
dataOutputStream.writeInt(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, 0));
+ dataOutputStream.writeBoolean(Flags.walletRoleEnabled());
dataOutputStream.flush();
} catch (IOException e) {
// Never happens for MessageDigestOutputStream and DataOutputStream.
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
index ed981e0aca74..2154a26acbaf 100644
--- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -4,5 +4,5 @@ flag {
name: "support_input_wakeup_delegate"
namespace: "wear_frameworks"
description: "Whether or not window policy allows injecting input wake-up delegate."
- bug: "298055811"
+ bug: "319132073"
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index a5b90f12a5cc..a580bb7c741a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -215,7 +215,8 @@ class Rollback {
/* packages */ new ArrayList<>(),
/* isStaged */ isStaged,
/* causePackages */ new ArrayList<>(),
- /* committedSessionId */ -1);
+ /* committedSessionId */ -1,
+ /* rollbackImpactLevel */ PackageManager.ROLLBACK_USER_IMPACT_LOW);
mUserId = userId;
mInstallerPackageName = installerPackageName;
mBackupDir = backupDir;
@@ -394,7 +395,8 @@ class Rollback {
*/
@WorkerThread
boolean enableForPackage(String packageName, long newVersion, long installedVersion,
- boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy) {
+ boolean isApex, String sourceDir, String[] splitSourceDirs, int rollbackDataPolicy,
+ @PackageManager.RollbackImpactLevel int rollbackImpactLevel) {
assertInWorkerThread();
try {
RollbackStore.backupPackageCodePath(this, packageName, sourceDir);
@@ -415,6 +417,10 @@ class Rollback {
isApex, false /* isApkInApex */, new ArrayList<>(), rollbackDataPolicy);
info.getPackages().add(packageRollbackInfo);
+
+ if (info.getRollbackImpactLevel() < rollbackImpactLevel) {
+ info.setRollbackImpactLevel(rollbackImpactLevel);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 13f114138261..359678b15213 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -954,7 +954,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
ApplicationInfo appInfo = pkgInfo.applicationInfo;
return rollback.enableForPackage(packageName, newPackage.getVersionCode(),
pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
- appInfo.splitSourceDirs, rollbackDataPolicy);
+ appInfo.splitSourceDirs, rollbackDataPolicy, session.rollbackImpactLevel);
}
@ExtThread
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 0af137faf5b4..14539d544bf9 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -193,16 +193,27 @@ class RollbackStore {
json.put("isStaged", rollback.isStaged());
json.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
json.put("committedSessionId", rollback.getCommittedSessionId());
+ if (Flags.recoverabilityDetection()) {
+ json.put("rollbackImpactLevel", rollback.getRollbackImpactLevel());
+ }
return json;
}
private static RollbackInfo rollbackInfoFromJson(JSONObject json) throws JSONException {
- return new RollbackInfo(
+ RollbackInfo rollbackInfo = new RollbackInfo(
json.getInt("rollbackId"),
packageRollbackInfosFromJson(json.getJSONArray("packages")),
json.getBoolean("isStaged"),
versionedPackagesFromJson(json.getJSONArray("causePackages")),
json.getInt("committedSessionId"));
+
+ if (Flags.recoverabilityDetection()) {
+ // to make it backward compatible.
+ rollbackInfo.setRollbackImpactLevel(json.optInt("rollbackImpactLevel",
+ PackageManager.ROLLBACK_USER_IMPACT_LOW));
+ }
+
+ return rollbackInfo;
}
/**
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 73fc8e9cfbc4..ac1b4df930eb 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3860,6 +3860,23 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void onVideoFreezeUpdated(boolean isFrozen) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onVideoFreezeUpdated(" + isFrozen + ")");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onVideoFreezeUpdated(isFrozen, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onVideoFreezeUpdated", e);
+ }
+ }
+ }
+
+ @Override
public void onContentAllowed() {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 7ab075e2f3a7..9c60fbb6bb9a 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1470,6 +1470,28 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void notifyVideoFreezeUpdated(IBinder sessionToken, boolean isFrozen, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "notifyVideoFreezeUpdated");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).notifyVideoFreezeUpdated(isFrozen);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in notifyVideoFreezeUpdated", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void notifyContentAllowed(IBinder sessionToken, int userId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -1557,7 +1579,6 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
-
@Override
public void notifyRecordingStarted(IBinder sessionToken, String recordingId,
String requestId, int userId) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1c90e30c256e..2049331e8efe 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -46,6 +46,7 @@
#include <com_android_input_flags.h>
#include <input/Input.h>
#include <input/PointerController.h>
+#include <input/PrintTools.h>
#include <input/SpriteController.h>
#include <inputflinger/InputManager.h>
#include <limits.h>
@@ -230,10 +231,6 @@ inline static T max(const T& a, const T& b) {
return a > b ? a : b;
}
-static inline const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
static SpriteIcon toSpriteIcon(PointerIcon pointerIcon) {
// As a minor optimization, do not make a copy of the PointerIcon bitmap here. The loaded
// PointerIcons are only cached by InputManagerService in java, so we can safely assume they
@@ -288,7 +285,7 @@ public:
void setSystemUiLightsOut(bool lightsOut);
void setPointerDisplayId(int32_t displayId);
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(bool enabled);
+ void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -304,6 +301,7 @@ public:
bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
int32_t displayId, DeviceId deviceId, int32_t pointerId,
const sp<IBinder>& inputToken);
+ void setPointerIconVisibility(int32_t displayId, bool visible);
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
@@ -400,8 +398,8 @@ private:
// Pointer speed.
int32_t pointerSpeed{0};
- // True if pointer acceleration is enabled for mice.
- bool mousePointerAccelerationEnabled{true};
+ // Displays on which its associated mice will have pointer acceleration disabled.
+ std::set<int32_t> displaysWithMousePointerAccelerationDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -492,8 +490,8 @@ void NativeInputManager::dump(std::string& dump) {
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
- dump += StringPrintf(INDENT "Mouse Pointer Acceleration: %s\n",
- mLocked.mousePointerAccelerationEnabled ? "Enabled" : "Disabled");
+ dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
+ dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -676,11 +674,13 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
std::scoped_lock _l(mLock);
outConfig->mousePointerSpeed = mLocked.pointerSpeed;
- outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled;
- outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
- * POINTER_SPEED_EXPONENT);
+ outConfig->displaysWithMousePointerAccelerationDisabled =
+ mLocked.displaysWithMousePointerAccelerationDisabled;
+ outConfig->pointerVelocityControlParameters.scale =
+ exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
- mLocked.mousePointerAccelerationEnabled
+ mLocked.displaysWithMousePointerAccelerationDisabled.count(
+ mLocked.pointerDisplayId) == 0
? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION
: 1;
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
@@ -1224,16 +1224,23 @@ void NativeInputManager::setPointerSpeed(int32_t speed) {
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(bool enabled) {
+void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
- if (mLocked.mousePointerAccelerationEnabled == enabled) {
+ const bool oldEnabled =
+ mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0;
+ if (oldEnabled == enabled) {
return;
}
- ALOGI("Setting mouse pointer acceleration to %s", toString(enabled));
- mLocked.mousePointerAccelerationEnabled = enabled;
+ ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled),
+ displayId);
+ if (enabled) {
+ mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
+ } else {
+ mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId);
+ }
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
@@ -1397,6 +1404,13 @@ bool NativeInputManager::setPointerIcon(
return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
}
+void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+ if (!ENABLE_POINTER_CHOREOGRAPHER) {
+ return;
+ }
+ mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
+}
+
TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
JNIEnv *env, jfloatArray matrixArr) {
ATRACE_CALL();
@@ -2168,10 +2182,10 @@ static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed
}
static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
- jboolean enabled) {
+ jint displayId, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(enabled);
+ im->setMousePointerAccelerationEnabled(displayId, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -2550,6 +2564,13 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico
ibinderForJavaObject(env, inputTokenObj));
}
+static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jboolean visible) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setPointerIconVisibility(displayId, visible);
+}
+
static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2791,7 +2812,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
(void*)nativeTransferTouchFocus},
{"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
- {"setMousePointerAccelerationEnabled", "(Z)V",
+ {"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
@@ -2828,6 +2849,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
(void*)nativeSetCustomPointerIcon},
{"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
(void*)nativeSetPointerIcon},
+ {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility},
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 62d2d7ee848a..4c74878dab70 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -22,6 +22,7 @@ import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.content.pm.SigningDetails
import android.os.Build
+import android.permission.flags.Flags
import android.util.Slog
import com.android.internal.os.RoSystemProperties
import com.android.internal.pm.permission.CompatibilityPermissionInfo
@@ -1197,15 +1198,80 @@ class AppIdPermissionPolicy : SchemePolicy() {
newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
.androidPackage!!
.signingDetails
- return sourceSigningDetails?.hasCommonSignerWithCapability(
- packageSigningDetails,
- SigningDetails.CertCapabilities.PERMISSION
- ) == true ||
- packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
- platformSigningDetails.checkCapability(
+ val hasCommonSigner =
+ sourceSigningDetails?.hasCommonSignerWithCapability(
packageSigningDetails,
SigningDetails.CertCapabilities.PERMISSION
- )
+ ) == true ||
+ packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+ platformSigningDetails.checkCapability(
+ packageSigningDetails,
+ SigningDetails.CertCapabilities.PERMISSION
+ )
+ if (!Flags.signaturePermissionAllowlistEnabled()) {
+ return hasCommonSigner;
+ }
+ if (!hasCommonSigner) {
+ return false
+ }
+ // A platform signature permission also needs to be allowlisted on non-debuggable builds.
+ if (permission.packageName == PLATFORM_PACKAGE_NAME) {
+ val isRequestedByFactoryApp =
+ if (packageState.isSystem) {
+ // For updated system applications, a signature permission still needs to be
+ // allowlisted if it wasn't requested by the original application.
+ if (packageState.isUpdatedSystemApp) {
+ val disabledSystemPackage =
+ newState.externalState.disabledSystemPackageStates[
+ packageState.packageName]
+ ?.androidPackage
+ disabledSystemPackage != null &&
+ permission.name in disabledSystemPackage.requestedPermissions
+ } else {
+ true
+ }
+ } else {
+ false
+ }
+ if (
+ !(isRequestedByFactoryApp ||
+ getSignaturePermissionAllowlistState(packageState, permission.name) == true)
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "Signature permission ${permission.name} for package" +
+ " ${packageState.packageName} (${packageState.path}) not in" +
+ " signature permission allowlist"
+ )
+ if (!Build.isDebuggable()) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ private fun MutateStateScope.getSignaturePermissionAllowlistState(
+ packageState: PackageState,
+ permissionName: String
+ ): Boolean? {
+ val permissionAllowlist = newState.externalState.permissionAllowlist
+ val packageName = packageState.packageName
+ return when {
+ packageState.isVendor || packageState.isOdm ->
+ permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName)
+ packageState.isProduct ->
+ permissionAllowlist.getProductSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ packageState.isSystemExt ->
+ permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+ }
}
private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index 4fcdbfc21f6c..d479e52f92d8 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -11,7 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
index 74506076d82f..c99e7129853b 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -41,17 +41,26 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit
private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk";
private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk";
+ // TODO: Move the silent installs to test-app using {@link
+ // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig
+ // branch in BICS.
+ // b/310983905
@Test
public void testGetMockBackgroundInstalledPackages() throws Exception {
- installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
+ installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1);
installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2);
assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull();
assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull();
- assertThat(getDevice().setProperty("debug.transparency.bg-install-apps",
- MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue();
- runDeviceTest("testGetMockBackgroundInstalledPackages");
+ assertThat(
+ getDevice()
+ .setProperty(
+ "debug.transparency.bg-install-apps",
+ MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2))
+ .isTrue();
+ runDeviceTest(
+ "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages");
assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull();
assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull();
@@ -65,10 +74,10 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit
assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue();
}
- private void runDeviceTest(String method) throws DeviceNotAvailableException {
+ private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException {
var options = new DeviceTestRunOptions(PACKAGE_NAME);
- options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest");
+ options.setTestClassName(PACKAGE_NAME + "." + testName);
options.setTestMethodName(method);
runDeviceTests(options);
}
-}
+} \ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
index 1fa1f84cd04e..cbe58a8fec70 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml
@@ -24,4 +24,4 @@
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="APCT tests for background install control service"
android:targetPackage="com.android.server.pm.test.app" />
-</manifest>
+</manifest> \ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
index b74e5619fd0c..b23f59106881 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -16,6 +16,10 @@
package com.android.server.pm.test.app;
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@@ -23,12 +27,15 @@ import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,31 +46,52 @@ import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class BackgroundInstallControlServiceTest {
private static final String TAG = "BackgroundInstallControlServiceTest";
+ private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";
private IBackgroundInstallControlService mIBics;
@Before
public void setUp() {
- mIBics = IBackgroundInstallControlService.Stub.asInterface(
- ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ mIBics =
+ IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
assertThat(mIBics).isNotNull();
}
+ @After
+ public void tearDown() {
+ runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME);
+ }
+
@Test
public void testGetMockBackgroundInstalledPackages() throws RemoteException {
- ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages(
- PackageManager.MATCH_ALL,
- UserHandle.USER_ALL);
+ ParceledListSlice<PackageInfo> slice =
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mIBics,
+ (bics) -> {
+ try {
+ return bics.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL, Process.myUserHandle()
+ .getIdentifier());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ GET_BACKGROUND_INSTALLED_PACKAGES);
assertThat(slice).isNotNull();
var packageList = slice.getList();
assertThat(packageList).isNotNull();
assertThat(packageList).hasSize(2);
- var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1",
- "com.android.servicestests.apps.bicmockapp2");
- var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName)
- .collect(Collectors.toSet());
+ var expectedPackageNames =
+ Set.of(
+ "com.android.servicestests.apps.bicmockapp1",
+ "com.android.servicestests.apps.bicmockapp2");
+ var actualPackageNames =
+ packageList.stream()
+ .map((packageInfo) -> packageInfo.packageName)
+ .collect(Collectors.toSet());
assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
}
-}
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
index d82e6abeb502..23e3e2560524 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,18 +26,13 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.io.File;
import java.util.List;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AdditionalSubtypeUtilsTest {
+public final class AdditionalSubtypeUtilsTest {
@Test
public void testSaveAndLoad() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
index 6eedeea8c333..b7223d6615b9 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/HardwareKeyboardShortcutControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,11 @@ package com.android.server.inputmethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.List;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
public final class HardwareKeyboardShortcutControllerTest {
@Test
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 7cbfc52c5cce..570132f5a7d8 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -28,6 +28,7 @@ import android.content.pm.ResolveInfo;
import android.util.ArrayMap;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -123,10 +124,12 @@ public class InputMethodManagerServiceRestrictImeAmountTest extends
private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
List<String> enabledComponents) {
+ final ArrayMap<String, List<InputMethodSubtype>> emptyAdditionalSubtypeMap =
+ new ArrayMap<>();
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- InputMethodManagerService.filterInputMethodServices(new ArrayMap<>(), methodMap, methodList,
- enabledComponents, mContext, resolveInfoList);
+ InputMethodManagerService.filterInputMethodServices(emptyAdditionalSubtypeMap, methodMap,
+ methodList, enabledComponents, mContext, resolveInfoList);
return methodList;
}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 0884b784ac73..fbe384a62658 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,22 +28,16 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodSubtypeSwitchingControllerTest {
+public final class InputMethodSubtypeSwitchingControllerTest {
private static final String DUMMY_PACKAGE_NAME = "dummy package name";
private static final String DUMMY_IME_LABEL = "dummy ime label";
private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 9688ef6cc83b..ac485be5b5a1 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,15 +41,12 @@ import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.inputmethod.StartInputFlags;
import com.google.common.truth.Truth;
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collections;
@@ -57,9 +54,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputMethodUtilsTest {
+public final class InputMethodUtilsTest {
private static final boolean IS_AUX = true;
private static final boolean IS_DEFAULT = true;
private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true;
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
index 01f8129c2cd7..d0b46f58d626 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,18 +22,12 @@ import static org.junit.Assert.assertEquals;
import android.os.LocaleList;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Locale;
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocaleUtilsTest {
+public final class LocaleUtilsTest {
private static final LocaleUtils.LocaleExtractor<Locale> sIdentityMapper = source -> source;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 978044045ab3..a185ad99a8f3 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -419,7 +419,7 @@ public class PackageUserStateTest {
"installerTitle");
packageUserState.setArchiveState(archiveState);
assertEquals(archiveState, packageUserState.getArchiveState());
- assertTrue(archiveState.getArchiveTimeMillis() > currentTimeMillis);
+ assertTrue(archiveState.getArchiveTimeMillis() >= currentTimeMillis);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 284e491f8081..93a2eefba5b1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -31,13 +31,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -76,6 +80,7 @@ import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.EmptyArray;
+import android.util.SparseArray;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.DeviceIdleInternal;
@@ -96,6 +101,7 @@ import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.time.Clock;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
@@ -182,7 +188,12 @@ public class FlexibilityControllerTest {
mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=50|60|70|80"
+ + ",400=50|60|70|80"
+ + ",300=50|60|70|80"
+ + ",200=50|60|70|80"
+ + ",100=50|60|70|80");
setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
waitForQuietModuleThread();
@@ -200,6 +211,11 @@ public class FlexibilityControllerTest {
}
}
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+ sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+ }
+
private void setDeviceConfigInt(String key, int val) {
mDeviceConfigPropertiesBuilder.setInt(key, val);
updateDeviceConfig(key);
@@ -250,9 +266,12 @@ public class FlexibilityControllerTest {
*/
@Test
public void testDefaultVariableValues() {
- assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
- mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
- );
+ SparseArray<int[]> defaultPercentsToDrop =
+ FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
+ defaultPercentsToDrop.valueAt(i).length);
+ }
}
@Test
@@ -379,10 +398,13 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_FallbackDeadline() {
JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
- assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
- setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
- assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.get(JobInfo.PRIORITY_DEFAULT),
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 123L);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=500,400=400,300=300,200=200,100=100");
+ assertEquals(300L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
}
@Test
@@ -392,10 +414,32 @@ public class FlexibilityControllerTest {
JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=1|2|3|4"
+ + ",400=5|6|7|8"
+ + ",300=10|20|30|40"
+ + ",200=50|51|52|53"
+ + ",100=54|55|56|57");
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_MAX),
+ new int[]{1, 2, 3, 4});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_HIGH),
+ new int[]{5, 6, 7, 8});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_DEFAULT),
+ new int[]{10, 20, 30, 40});
assertArrayEquals(
- mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
- new int[] {10, 20, 30, 40});
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_LOW),
+ new int[]{50, 51, 52, 53});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_MIN),
+ new int[]{54, 55, 56, 57});
assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.setNumDroppedFlexibleConstraints(1);
@@ -408,25 +452,64 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
- JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
- js.enqueueTime = JobSchedulerService.sElapsedRealtimeClock.millis();
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ // No priority mapping
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+ final SparseArray<int[]> defaultPercentsToDrop =
+ FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ final SparseArray<int[]> percentsToDrop =
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Invalid priority-percentList string
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10,20a,030,40"
+ + ",400=20|40|60|80"
+ + ",300=25|50|75|80"
+ + ",200=40|50|60|80"
+ + ",100=20|40|60|80");
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Invalid percentList strings
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10|20a|030|40" // Letters
+ + ",400=10|40" // Not enough
+ + ",300=.|50|_|80" // Characters
+ + ",200=50|40|10|40" // Out of order
+ + ",100=30|60|90|101"); // Over 100
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Only partially invalid
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10|20a|030|40" // Letters
+ + ",400=10|40" // Not enough
+ + ",300=.|50|_|80" // Characters
+ + ",200=10|20|30|40" // Valid
+ + ",100=20|40|60|80"); // Valid
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_MAX),
+ percentsToDrop.get(JobInfo.PRIORITY_MAX));
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_HIGH),
+ percentsToDrop.get(JobInfo.PRIORITY_HIGH));
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_DEFAULT),
+ percentsToDrop.get(JobInfo.PRIORITY_DEFAULT));
+ assertArrayEquals(new int[]{10, 20, 30, 40}, percentsToDrop.get(JobInfo.PRIORITY_LOW));
+ assertArrayEquals(new int[]{20, 40, 60, 80}, percentsToDrop.get(JobInfo.PRIORITY_MIN));
}
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 25 * HOUR_IN_MILLIS
+ + ",300=" + 50 * HOUR_IN_MILLIS
+ + ",200=" + 100 * HOUR_IN_MILLIS
+ + ",100=" + 200 * HOUR_IN_MILLIS);
long nextTimeToDropNumConstraints;
@@ -459,34 +542,34 @@ public class FlexibilityControllerTest {
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) / 2,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 6 / 10,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 7 / 10,
nextTimeToDropNumConstraints);
// no delay, no deadline
- jb = createJob(0);
+ jb = createJob(0).setPriority(JobInfo.PRIORITY_LOW);
js = createJobStatus("time", jb);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0)
@@ -514,13 +597,13 @@ public class FlexibilityControllerTest {
@Test
public void testCurPercent() {
long deadline = 100 * MINUTE_IN_MILLIS;
- long nowElapsed;
+ long nowElapsed = FROZEN_TIME;
JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
JobStatus js = createJobStatus("time", jb);
assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(deadline + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -547,7 +630,8 @@ public class FlexibilityControllerTest {
assertEquals(FROZEN_TIME + delay,
mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(deadline + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
+ FROZEN_TIME + delay));
nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
@@ -670,34 +754,63 @@ public class FlexibilityControllerTest {
@Test
public void testGetLifeCycleEndElapsedLocked_Prefetch() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
// prefetch no estimate
JobInfo.Builder jb = createJob(0).setPrefetch(true);
JobStatus js = createJobStatus("time", jb);
doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
- assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ assertEquals(Long.MAX_VALUE,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
// prefetch with estimate
jb = createJob(0).setPrefetch(true);
js = createJobStatus("time", jb);
doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
- assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ assertEquals(1000L,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
}
@Test
public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 2 * HOUR_IN_MILLIS
+ + ",300=" + 3 * HOUR_IN_MILLIS
+ + ",200=" + 4 * HOUR_IN_MILLIS
+ + ",100=" + 5 * HOUR_IN_MILLIS);
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
// deadline
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
// no deadline
- jb = createJob(0);
- js = createJobStatus("time", jb);
- assertEquals(FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+ assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+ nowElapsed, 100L));
}
@Test
public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
@@ -705,20 +818,91 @@ public class FlexibilityControllerTest {
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+ }
+
+ @Test
+ public void testGetLifeCycleEndElapsedLocked_ScoreAddition() {
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + HOUR_IN_MILLIS
+ + ",300=" + HOUR_IN_MILLIS
+ + ",200=" + HOUR_IN_MILLIS
+ + ",100=" + HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ "500=5,400=4,300=3,200=2,100=1");
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ "500=" + 5 * MINUTE_IN_MILLIS
+ + ",400=" + 4 * MINUTE_IN_MILLIS
+ + ",300=" + 3 * MINUTE_IN_MILLIS
+ + ",200=" + 2 * MINUTE_IN_MILLIS
+ + ",100=" + 1 * MINUTE_IN_MILLIS);
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
+ JobStatus jsMax = createJobStatus("testScoreCalculation",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+ JobStatus jsHigh = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+ // Make score = 15
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ mFlexibilityController.prepareForExecutionLocked(jsHigh);
+ mFlexibilityController.prepareForExecutionLocked(jsDefault);
+ mFlexibilityController.prepareForExecutionLocked(jsLow);
+ mFlexibilityController.prepareForExecutionLocked(jsMin);
+
+ // deadline
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
+ JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
+ assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
+ final long earliestMs = 123L;
+ // no deadline
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 4 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 1 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+ nowElapsed, earliestMs));
}
@Test
@@ -734,6 +918,14 @@ public class FlexibilityControllerTest {
@Test
public void testFlexibilityTracker() {
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + 100 * HOUR_IN_MILLIS
+ + ",400=" + 100 * HOUR_IN_MILLIS
+ + ",300=" + 100 * HOUR_IN_MILLIS
+ + ",200=" + 100 * HOUR_IN_MILLIS
+ + ",100=" + 100 * HOUR_IN_MILLIS);
+
FlexibilityController.FlexibilityTracker flexTracker =
mFlexibilityController.new FlexibilityTracker(4);
// Plus one for jobs with 0 required constraint.
@@ -805,8 +997,7 @@ public class FlexibilityControllerTest {
assertEquals(1, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
- final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
- + HOUR_IN_MILLIS);
+ final long nowElapsed = 51 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -1220,66 +1411,161 @@ public class FlexibilityControllerTest {
@Test
public void testCalculateNumDroppedConstraints() {
- JobInfo.Builder jb = createJob(22);
- JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
- long nowElapsed = FROZEN_TIME;
-
- mFlexibilityController.mFlexibilityTracker.add(js);
-
- assertEquals(3, js.getNumRequiredFlexibleConstraints());
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 5 * HOUR_IN_MILLIS
+ + ",300=" + 8 * HOUR_IN_MILLIS
+ + ",200=" + 10 * HOUR_IN_MILLIS
+ + ",100=" + 20 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=20|40|60|80"
+ + ",400=20|40|60|80"
+ + ",300=25|50|75|80"
+ + ",200=40|50|60|80"
+ + ",100=20|40|60|80");
+
+ JobStatus jsHigh = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(24).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(23).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(22).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(21).setPriority(JobInfo.PRIORITY_MIN));
+ final long startElapsed = FROZEN_TIME;
+ long nowElapsed = startElapsed;
+
+ mFlexibilityController.mFlexibilityTracker.add(jsHigh);
+ mFlexibilityController.mFlexibilityTracker.add(jsDefault);
+ mFlexibilityController.mFlexibilityTracker.add(jsLow);
+ mFlexibilityController.mFlexibilityTracker.add(jsMin);
+
+ assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(4, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
- nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
+ nowElapsed = startElapsed + HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
mFlexibilityController.mFlexibilityTracker
- .setNumDroppedFlexibleConstraints(js, 1);
-
- assertEquals(2, js.getNumRequiredFlexibleConstraints());
- assertEquals(1, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
- .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
-
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(2, js.getNumRequiredFlexibleConstraints());
- assertEquals(1, js.getNumDroppedFlexibleConstraints());
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(2, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(3, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
- nowElapsed = FROZEN_TIME;
+ nowElapsed = startElapsed;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(3, js.getNumRequiredFlexibleConstraints());
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(4, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
- nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
+ nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(0, js.getNumRequiredFlexibleConstraints());
- assertEquals(3, js.getNumDroppedFlexibleConstraints());
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(2, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
- nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
+ nowElapsed = startElapsed + 4 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(1, js.getNumRequiredFlexibleConstraints());
- assertEquals(2, js.getNumDroppedFlexibleConstraints());
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(1, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(2, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(2, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
}
@Test
@@ -1308,7 +1594,7 @@ public class FlexibilityControllerTest {
.get(js.getSourceUserId(), js.getSourcePackageName()));
assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(1150L,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 150L));
assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
assertEquals(650L, mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js));
@@ -1317,6 +1603,80 @@ public class FlexibilityControllerTest {
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
}
+ @Test
+ public void testScoreCalculation() {
+ JobStatus jsMax = createJobStatus("testScoreCalculation",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+ JobStatus jsHigh = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+ long nowElapsed = sElapsedRealtimeClock.millis();
+ assertEquals(0,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(5,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+ nowElapsed += 30 * MINUTE_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(10,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(31 * MINUTE_IN_MILLIS);
+ nowElapsed += 31 * MINUTE_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsHigh);
+ assertEquals(14,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(2 * HOUR_IN_MILLIS);
+ nowElapsed += 2 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsDefault);
+ assertEquals(17,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsLow);
+ assertEquals(19,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMin);
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(25,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+ mFlexibilityController.unprepareFromExecutionLocked(jsMax);
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ // 24 hours haven't passed yet. The jobs in the first hour bucket should still be included.
+ advanceElapsedClock(12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1);
+ nowElapsed += 12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1;
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ // Passed the 24 hour mark. The jobs in the first hour bucket should no longer be included.
+ advanceElapsedClock(2);
+ nowElapsed += 2;
+ assertEquals(10,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ }
+
/**
* The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
* the estimated launch time was updated and the last time the app was opened.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index ec7e35982311..e989d7b060be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -552,20 +552,22 @@ public class PackageArchiverTest {
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isNull();
}
@Test
public void getArchivedAppIcon_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
- mIcon);
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT,
+ CALLER_PACKAGE)).isEqualTo(mIcon);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e6298eeccafb..5bec903e6414 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -569,6 +569,23 @@ public final class UserManagerServiceTest {
}
@Test
+ public void testAutoLockPrivateProfile() {
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+ any());
+
+ mSpiedUms.autoLockPrivateSpace();
+
+ Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
public void testAutoLockOnDeviceLockForPrivateProfile() {
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
diff --git a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
index 9c8276aac4dd..84c0ab38ca48 100644
--- a/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/LoudnessCodecHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.audio;
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_DEVICE_ID;
import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4;
@@ -22,6 +23,7 @@ import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_T
import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
+import static android.os.Process.myPid;
import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_LARGE;
import static com.android.server.audio.LoudnessCodecHelper.SPL_RANGE_MEDIUM;
@@ -37,6 +39,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -64,13 +67,16 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
@RunWith(AndroidJUnit4.class)
@Presubmit
public class LoudnessCodecHelperTest {
private static final String TAG = "LoudnessCodecHelperTest";
+ private static final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
+
+ private static final int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_MUSIC;
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -83,94 +89,84 @@ public class LoudnessCodecHelperTest {
private final int mInitialApcPiid = 1;
+ private int mSessionId;
+
@Before
public void setUp() throws Exception {
mLoudnessHelper = new LoudnessCodecHelper(mAudioService);
+ mSessionId = 1;
when(mAudioService.getActivePlaybackConfigurations()).thenReturn(
- getApcListForPiids(mInitialApcPiid));
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 0));
when(mDispatcher.asBinder()).thenReturn(Mockito.mock(IBinder.class));
}
@Test
- public void registerDispatcher_sendsInitialUpdateOnStart() throws Exception {
+ public void registerDispatcher_sendsUpdateOnAddCodec() throws Exception {
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
+ getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid), any());
+ verify(mDispatcher).dispatchLoudnessCodecParameterChange(eq(mSessionId), any());
}
@Test
- public void unregisterDispatcher_noInitialUpdateOnStart() throws Exception {
+ public void unregisterDispatcher_noUpdateOnAdd() throws Exception {
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
mLoudnessHelper.unregisterLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/false, CODEC_METADATA_TYPE_MPEG_D)));
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
+ getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@Test
- public void addCodecInfo_sendsInitialUpdateAfterStart() throws Exception {
+ public void addCodecInfoForDifferentId_noUpdateSent() throws Exception {
+ final int newSessionId = mSessionId + 1;
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
- mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(newSessionId, /*mediaCodecHash=*/222,
getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@Test
- public void addCodecInfoForUnstartedPiid_noUpdateSent() throws Exception {
- final int newPiid = 2;
+ public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception {
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/true,
- CODEC_METADATA_TYPE_MPEG_4)));
- mLoudnessHelper.addLoudnessCodecInfo(newPiid, /*mediaCodecHash=*/222,
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222,
getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
- any());
- }
+ mLoudnessHelper.updateCodecParameters(
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
- @Test
- public void updateCodecParameters_updatesOnlyStartedPiids() throws Exception {
- final int newPiid = 2;
- mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
-
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_4)));
- //does not trigger dispatch since active apc list does not contain newPiid
- mLoudnessHelper.startLoudnessCodecUpdates(newPiid,
- List.of(getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D)));
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ // no dispatch since mSessionId was not started
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
-
- // triggers dispatch for new active apc with newPiid
- mLoudnessHelper.updateCodecParameters(getApcListForPiids(newPiid));
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(newPiid), any());
}
@Test
- public void updateCodecParameters_noStartedPiids_noDispatch() throws Exception {
+ public void updateCodecParameters_dispatchUpdates() throws Exception {
+ final LoudnessCodecInfo info = getLoudnessInfo(/*isDownmixing=*/true,
+ CODEC_METADATA_TYPE_MPEG_4);
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.addLoudnessCodecInfo(mInitialApcPiid, /*mediaCodecHash=*/222,
- getLoudnessInfo(/*isDownmixing=*/true, CODEC_METADATA_TYPE_MPEG_D));
- mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info);
- // no dispatch since mInitialApcPiid was not started
- verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ mLoudnessHelper.updateCodecParameters(
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
+
+ // second dispatch since player configurations were updated
+ verify(mDispatcher, times(2)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@@ -180,13 +176,15 @@ public class LoudnessCodecHelperTest {
CODEC_METADATA_TYPE_MPEG_4);
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
- mLoudnessHelper.removeLoudnessCodecInfo(mInitialApcPiid, info);
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.addLoudnessCodecInfo(mSessionId, /*mediaCodecHash=*/222, info);
+ mLoudnessHelper.removeLoudnessCodecInfo(mSessionId, info);
- mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+ mLoudnessHelper.updateCodecParameters(
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
// no second dispatch since codec info was removed for updates
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@@ -196,13 +194,14 @@ public class LoudnessCodecHelperTest {
CODEC_METADATA_TYPE_MPEG_4);
mLoudnessHelper.registerLoudnessCodecUpdatesDispatcher(mDispatcher);
- mLoudnessHelper.startLoudnessCodecUpdates(mInitialApcPiid, List.of(info));
- mLoudnessHelper.stopLoudnessCodecUpdates(mInitialApcPiid);
+ mLoudnessHelper.startLoudnessCodecUpdates(mSessionId);
+ mLoudnessHelper.stopLoudnessCodecUpdates(mSessionId);
- mLoudnessHelper.updateCodecParameters(getApcListForPiids(mInitialApcPiid));
+ mLoudnessHelper.updateCodecParameters(
+ getApcListForApcWithPiidSid(mInitialApcPiid, mSessionId, 1));
// no second dispatch since piid was removed for updates
- verify(mDispatcher, times(1)).dispatchLoudnessCodecParameterChange(eq(mInitialApcPiid),
+ verify(mDispatcher, times(0)).dispatchLoudnessCodecParameterChange(eq(mSessionId),
any());
}
@@ -308,23 +307,28 @@ public class LoudnessCodecHelperTest {
assertEquals(6, loudnessParameters.getInt(KEY_AAC_DRC_EFFECT_TYPE));
}
- private List<AudioPlaybackConfiguration> getApcListForPiids(int... piids) {
+ private List<AudioPlaybackConfiguration> getApcListForApcWithPiidSid(int piid, int sessionId,
+ int devIdx) {
final ArrayList<AudioPlaybackConfiguration> apcList = new ArrayList<>();
AudioDeviceInfo[] devicesStatic = AudioManager.getDevicesStatic(GET_DEVICES_OUTPUTS);
- assumeTrue(devicesStatic.length > 0);
- int index = new Random().nextInt(devicesStatic.length);
- Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + index);
- int deviceId = devicesStatic[index].getId();
-
- for (int piid : piids) {
- PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
- AudioPlaybackConfiguration apc =
- new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/1);
- apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
-
- apcList.add(apc);
- }
+ assumeTrue(devIdx < devicesStatic.length);
+ Log.d(TAG, "Out devices number " + devicesStatic.length + ". Picking index " + devIdx);
+ int deviceId = devicesStatic[devIdx].getId();
+
+ PlayerBase.PlayerIdCard idCard = Mockito.mock(PlayerBase.PlayerIdCard.class);
+ AudioPlaybackConfiguration apc =
+ new AudioPlaybackConfiguration(idCard, piid, /*uid=*/1, /*pid=*/myPid());
+ apc.handleStateEvent(PLAYER_UPDATE_DEVICE_ID, deviceId);
+ apc.handleSessionIdEvent(sessionId);
+ apc.handleAudioAttributesEvent(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
+ .build());
+
+ apcList.add(apc);
+
return apcList;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index a8eace05de97..c7300bbb50a7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -20,6 +20,7 @@ import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
@@ -33,11 +34,15 @@ import android.content.Intent;
import android.hardware.biometrics.AuthenticateOptions;
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricContextListener.FoldState;
+import android.hardware.biometrics.common.DisplayState;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import android.view.Display;
import android.view.DisplayInfo;
@@ -72,6 +77,9 @@ public class BiometricContextProviderTest {
@Rule
public TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock
private IStatusBarService mStatusBarService;
@@ -395,6 +403,37 @@ public class BiometricContextProviderTest {
}
}
+ @Test
+ public void testSubscribe_thenStartHal() throws RemoteException {
+ Consumer<OperationContext> updateConsumer = mock(Consumer.class);
+ Consumer<OperationContext> startHalConsumer = mock(Consumer.class);
+ AuthenticateOptions options = new FingerprintAuthenticateOptions.Builder().build();
+ OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+ assertThat(context.getDisplayState()).isEqualTo(DisplayState.UNKNOWN);
+ assertThat(context.getFoldState()).isEqualTo(IBiometricContextListener.FoldState.UNKNOWN);
+
+ mListener.onDisplayStateChanged(DisplayState.LOCKSCREEN);
+ mListener.onFoldChanged(FoldState.FULLY_CLOSED);
+ mProvider.subscribe(context, startHalConsumer, updateConsumer, options);
+
+ assertThat(context.getDisplayState()).isEqualTo(DisplayState.LOCKSCREEN);
+ assertThat(context.getFoldState()).isEqualTo(FoldState.FULLY_CLOSED);
+ verify(updateConsumer, never()).accept(context.toAidlContext());
+ verify(startHalConsumer).accept(context.toAidlContext(options));
+ }
+
+ @Test
+ public void testSubscribe_withInvalidOptions() {
+ Consumer<OperationContext> updateConsumer = mock(Consumer.class);
+ Consumer<OperationContext> startHalConsumer = mock(Consumer.class);
+ AuthenticateOptions options = mock(AuthenticateOptions.class);
+ OperationContextExt context = mProvider.updateContext(mOpContext, false /* crypto */);
+
+ assertThrows(IllegalStateException.class, () -> mProvider.subscribe(
+ context, startHalConsumer, updateConsumer, options));
+ }
+
private static byte reason(int type) {
if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) {
return OperationReason.BIOMETRIC_PROMPT;
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index daf18edaf2de..8656f60afc1e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -9,13 +9,16 @@
*
* 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.
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.Ã¥
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -27,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -97,7 +101,6 @@ public final class BackgroundInstallControlServiceTest {
private Looper mLooper;
private File mFile;
-
@Mock
private Context mContext;
@Mock
@@ -108,8 +111,10 @@ public final class BackgroundInstallControlServiceTest {
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock
private PermissionManagerServiceInternal mPermissionManager;
+
@Captor
private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
+
@Captor
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@@ -119,11 +124,12 @@ public final class BackgroundInstallControlServiceTest {
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
- mFile = new File(
- InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
- "test");
- mBackgroundInstallControlService = new BackgroundInstallControlService(
- new MockInjector(mContext));
+ mFile =
+ new File(
+ InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+ "test");
+ mBackgroundInstallControlService =
+ new BackgroundInstallControlService(new MockInjector(mContext));
verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
mUsageEventListener = mUsageEventListenerCaptor.getValue();
@@ -143,8 +149,7 @@ public final class BackgroundInstallControlServiceTest {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
mBackgroundInstallControlService.initBackgroundInstalledPackages();
assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- assertEquals(0,
- mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+ assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
}
@Test
@@ -161,12 +166,9 @@ public final class BackgroundInstallControlServiceTest {
// Write test data to the file on the disk.
try {
ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
- long token = protoOutputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+ long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+ protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
protoOutputStream.end(token);
protoOutputStream.flush();
atomicFile.finishWrite(fileOutputStream);
@@ -198,20 +200,14 @@ public final class BackgroundInstallControlServiceTest {
try {
ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
- long token = protoOutputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+ long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+ protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
protoOutputStream.end(token);
- token = protoOutputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
- protoOutputStream.write(
- BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
+ token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
+ protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
protoOutputStream.end(token);
protoOutputStream.flush();
@@ -241,7 +237,7 @@ public final class BackgroundInstallControlServiceTest {
// Read the file on the disk to verify
var packagesInDisk = new SparseSetArray<>();
AtomicFile atomicFile = new AtomicFile(mFile);
- try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -249,23 +245,25 @@ public final class BackgroundInstallControlServiceTest {
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
continue;
}
- long token = protoInputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ long token =
+ protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
String packageName = null;
int userId = UserHandle.USER_NULL;
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (protoInputStream.getFieldNumber()) {
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
- packageName = protoInputStream.readString(
- BackgroundInstalledPackageProto.PACKAGE_NAME);
+ packageName =
+ protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
break;
case (int) BackgroundInstalledPackageProto.USER_ID:
- userId = protoInputStream.readInt(
- BackgroundInstalledPackageProto.USER_ID) - 1;
+ userId =
+ protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID)
+ - 1;
break;
default:
- fail("Undefined field in proto: "
- + protoInputStream.getFieldNumber());
+ fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
}
}
protoInputStream.end(token);
@@ -296,7 +294,7 @@ public final class BackgroundInstallControlServiceTest {
// Read the file on the disk to verify
var packagesInDisk = new SparseSetArray<>();
AtomicFile atomicFile = new AtomicFile(mFile);
- try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -304,23 +302,25 @@ public final class BackgroundInstallControlServiceTest {
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
continue;
}
- long token = protoInputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ long token =
+ protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
String packageName = null;
int userId = UserHandle.USER_NULL;
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (protoInputStream.getFieldNumber()) {
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
- packageName = protoInputStream.readString(
- BackgroundInstalledPackageProto.PACKAGE_NAME);
+ packageName =
+ protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
break;
case (int) BackgroundInstalledPackageProto.USER_ID:
- userId = protoInputStream.readInt(
- BackgroundInstalledPackageProto.USER_ID) - 1;
+ userId =
+ protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID)
+ - 1;
break;
default:
- fail("Undefined field in proto: "
- + protoInputStream.getFieldNumber());
+ fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
}
}
protoInputStream.end(token);
@@ -353,7 +353,7 @@ public final class BackgroundInstallControlServiceTest {
// Read the file on the disk to verify
var packagesInDisk = new SparseSetArray<>();
AtomicFile atomicFile = new AtomicFile(mFile);
- try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -361,23 +361,25 @@ public final class BackgroundInstallControlServiceTest {
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
continue;
}
- long token = protoInputStream.start(
- BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ long token =
+ protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
String packageName = null;
int userId = UserHandle.USER_NULL;
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (protoInputStream.getFieldNumber()) {
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
- packageName = protoInputStream.readString(
- BackgroundInstalledPackageProto.PACKAGE_NAME);
+ packageName =
+ protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
break;
case (int) BackgroundInstalledPackageProto.USER_ID:
- userId = protoInputStream.readInt(
- BackgroundInstalledPackageProto.USER_ID) - 1;
+ userId =
+ protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID)
+ - 1;
break;
default:
- fail("Undefined field in proto: "
- + protoInputStream.getFieldNumber());
+ fail("Undefined field in proto: " + protoInputStream.getFieldNumber());
}
}
protoInputStream.end(token);
@@ -399,51 +401,55 @@ public final class BackgroundInstallControlServiceTest {
@Test
public void testHandleUsageEvent_permissionDenied() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, 0);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
}
@Test
public void testHandleUsageEvent_permissionGranted() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, 0);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
- assertEquals(1,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ assertEquals(
+ 1, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
}
@Test
public void testHandleUsageEvent_ignoredEvent() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
- USER_ID_1, INSTALLER_NAME_1, 0);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(UsageEvents.Event.USER_INTERACTION, USER_ID_1, INSTALLER_NAME_1, 0);
mTestLooper.dispatchAll();
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
}
@Test
public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
mTestLooper.dispatchAll();
var installerForegroundTimeFrames =
@@ -461,14 +467,18 @@ public final class BackgroundInstallControlServiceTest {
@Test
public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
mTestLooper.dispatchAll();
var installerForegroundTimeFrames =
@@ -486,16 +496,23 @@ public final class BackgroundInstallControlServiceTest {
@Test
public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_3);
mTestLooper.dispatchAll();
var installerForegroundTimeFrames =
@@ -517,12 +534,13 @@ public final class BackgroundInstallControlServiceTest {
@Test
public void testHandleUsageEvent_firstNoneActivityResumed() {
- assertEquals(0,
- mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ assertEquals(
+ 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
mTestLooper.dispatchAll();
var installerForegroundTimeFrames =
@@ -535,27 +553,26 @@ public final class BackgroundInstallControlServiceTest {
}
@Test
- public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedNoUsageEvent()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ INSTALLER_NAME_1,
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -572,27 +589,26 @@ public final class BackgroundInstallControlServiceTest {
}
@Test
- public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedInsideTimeFrame()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ INSTALLER_NAME_1,
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -604,12 +620,16 @@ public final class BackgroundInstallControlServiceTest {
// The 2 usage events make the package adding inside a time frame.
// So it's not a background install. Thus, it's null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
mTestLooper.dispatchAll();
@@ -617,27 +637,26 @@ public final class BackgroundInstallControlServiceTest {
}
@Test
- public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ INSTALLER_NAME_1,
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -650,12 +669,16 @@ public final class BackgroundInstallControlServiceTest {
// Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
// it's a background install. Thus, it's not null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
mTestLooper.dispatchAll();
@@ -665,28 +688,28 @@ public final class BackgroundInstallControlServiceTest {
assertEquals(1, packages.size());
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
}
+
@Test
- public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedOutsideTimeFrame2()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ INSTALLER_NAME_1,
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -700,12 +723,16 @@ public final class BackgroundInstallControlServiceTest {
// Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
// it's a background install. Thus, it's not null for the return of
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_2,
+ INSTALLER_NAME_2,
+ USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
mTestLooper.dispatchAll();
@@ -715,31 +742,31 @@ public final class BackgroundInstallControlServiceTest {
assertEquals(1, packages.size());
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
}
+
@Test
- public void testHandleUsageEvent_packageAddedThroughAdb() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedThroughAdb()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
// This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
// initiatingPackageName used to be null but is now "com.android.shell". This test ensures
// that the behavior is still the same for when the initiatingPackageName is null.
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ null,
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ null,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
// b/265203007
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -751,12 +778,16 @@ public final class BackgroundInstallControlServiceTest {
// for ADB installs the initiatingPackageName used to be null, despite being detected
// as a background install. Since we do not want to treat side-loaded apps as background
// install getBackgroundInstalledPackages() is expected to return null
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
mTestLooper.dispatchAll();
@@ -764,31 +795,31 @@ public final class BackgroundInstallControlServiceTest {
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
assertNull(packages);
}
+
@Test
- public void testHandleUsageEvent_packageAddedThroughAdb2() throws
- NoSuchFieldException, PackageManager.NameNotFoundException {
+ public void testHandleUsageEvent_packageAddedThroughAdb2()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
// This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the
// initiatingPackageName used to be null but is now "com.android.shell". This test ensures
// that the behavior is still the same after this change.
- InstallSourceInfo installSourceInfo = new InstallSourceInfo(
- /* initiatingPackageName = */ "com.android.shell",
- /* initiatingPackageSigningInfo = */ null,
- /* originatingPackageName = */ null,
- /* installingPackageName = */ INSTALLER_NAME_1);
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ "com.android.shell",
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
// b/265203007
when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
ApplicationInfo appInfo = mock(ApplicationInfo.class);
- when(mPackageManager.getApplicationInfoAsUser(
- eq(PACKAGE_NAME_1),
- any(),
- anyInt())
- ).thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
- long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
- - (System.currentTimeMillis() - SystemClock.uptimeMillis());
- FieldSetter.setField(appInfo,
+ long createTimestamp =
+ PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(
+ appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
createTimestamp);
@@ -800,12 +831,16 @@ public final class BackgroundInstallControlServiceTest {
// for ADB installs the initiatingPackageName is com.android.shell, despite being detected
// as a background install. Since we do not want to treat side-loaded apps as background
// install getBackgroundInstalledPackages() is expected to return null
- doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
- anyString(), anyString(), anyInt(), anyInt());
- generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
- generateUsageEvent(Event.ACTIVITY_STOPPED,
- USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyInt(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
mTestLooper.dispatchAll();
@@ -813,6 +848,7 @@ public final class BackgroundInstallControlServiceTest {
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
assertNull(packages);
}
+
@Test
public void testPackageRemoved() {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -859,8 +895,7 @@ public final class BackgroundInstallControlServiceTest {
packages.add(packageInfo2);
var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
packages.add(packageInfo3);
- doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(
- any(), anyInt());
+ doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(any(), anyInt());
var resultPackages =
mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
@@ -870,18 +905,30 @@ public final class BackgroundInstallControlServiceTest {
assertFalse(resultPackages.getList().contains(packageInfo3));
}
+ @Test(expected = SecurityException.class)
+ public void enforceCallerPermissionsThrowsSecurityException() {
+ doThrow(new SecurityException("test")).when(mContext)
+ .enforceCallingOrSelfPermission(eq(GET_BACKGROUND_INSTALLED_PACKAGES), anyString());
+
+ mBackgroundInstallControlService.enforceCallerPermissions();
+ }
+
+ @Test
+ public void enforceCallerPermissionsDoesNotThrowSecurityException() {
+ //enforceCallerQueryPackagesPermissions do not throw
+
+ mBackgroundInstallControlService.enforceCallerPermissions();
+ }
+
/**
* Mock a usage event occurring.
*
* @param usageEventId id of a usage event
- * @param userId user id of a usage event
- * @param pkgName package name of a usage event
- * @param timestamp timestamp of a usage event
+ * @param userId user id of a usage event
+ * @param pkgName package name of a usage event
+ * @param timestamp timestamp of a usage event
*/
- private void generateUsageEvent(int usageEventId,
- int userId,
- String pkgName,
- long timestamp) {
+ private void generateUsageEvent(int usageEventId, int userId, String pkgName, long timestamp) {
Event event = new Event(usageEventId, timestamp);
event.mPackage = pkgName;
mUsageEventListener.onUsageEvent(userId, event);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 080548520b0c..81df597f3f33 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -157,6 +157,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return mMockDevicePolicyManager;
case Context.APP_SEARCH_SERVICE:
case Context.ROLE_SERVICE:
+ case Context.APP_OPS_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
return getTestContext().getSystemService(name);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 9d56a36196bb..5e11e17f9414 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -18,6 +18,7 @@ package com.android.server.rollback;
import static com.google.common.truth.Truth.assertThat;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.util.SparseIntArray;
@@ -81,7 +82,8 @@ public class RollbackStoreTest {
+ "'installedUsers':[55,79],"
+ "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
+ "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
- + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
+ + "'committedSessionId':45654465, 'rollbackImpactLevel':1},"
+ + "'timestamp':'2019-10-01T12:29:08.855Z',"
+ "'originalSessionId':567,'state':'enabling','apkSessionId':-1,"
+ "'restoreUserDataInProgress':true, 'userId':0,"
+ "'installerPackageName':'some.installer'}";
@@ -138,6 +140,8 @@ public class RollbackStoreTest {
assertThat(rollback.getOriginalSessionId()).isEqualTo(567);
assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
assertThat(rollback.info.getPackages()).isEmpty();
+ assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
assertThat(rollback.isEnabling()).isTrue();
assertThat(rollback.getExtensionVersions().toString())
.isEqualTo(extensionVersions.toString());
@@ -158,6 +162,8 @@ public class RollbackStoreTest {
assertThat(rollback.info.getRollbackId()).isEqualTo(ID);
assertThat(rollback.info.getPackages()).isEmpty();
+ assertThat(rollback.info.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
assertThat(rollback.isEnabling()).isTrue();
assertThat(rollback.getExtensionVersions().toString())
.isEqualTo(extensionVersions.toString());
@@ -175,6 +181,7 @@ public class RollbackStoreTest {
origRb.info.getCausePackages().add(new VersionedPackage("com.made.up", 2));
origRb.info.getCausePackages().add(new VersionedPackage("com.pack.age", 99));
origRb.info.setCommittedSessionId(123456);
+ origRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
PackageRollbackInfo pkgInfo1 =
new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
@@ -226,6 +233,7 @@ public class RollbackStoreTest {
expectedRb.info.getCausePackages().add(new VersionedPackage("hello", 23));
expectedRb.info.getCausePackages().add(new VersionedPackage("something", 999));
expectedRb.info.setCommittedSessionId(45654465);
+ expectedRb.info.setRollbackImpactLevel(PackageManager.ROLLBACK_USER_IMPACT_HIGH);
PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
new VersionedPackage("blah1", 50), new ArrayList<>(), new ArrayList<>(),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index ea948ca0e28b..863cda4905f1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -118,12 +118,19 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
// Constructors that should be used to create instances of specific classes. Overrides scoring.
private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS;
+ // Setter methods that receive String parameters, but where those Strings represent Uris
+ // (and are visited/validated).
+ private static final ImmutableSet<Method> SETTERS_WITH_STRING_AS_URI;
+
static {
try {
PREFERRED_CONSTRUCTORS = ImmutableMap.of(
Notification.Builder.class,
Notification.Builder.class.getConstructor(Context.class, String.class));
+ SETTERS_WITH_STRING_AS_URI = ImmutableSet.of(
+ Person.Builder.class.getMethod("setUri", String.class));
+
EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder()
.put(RemoteViews.class,
// b/245950570: Tries to connect to service and will crash.
@@ -257,7 +264,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
@Nullable Class<?> styleClass, @Nullable Class<?> extenderClass,
@Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) {
SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context);
- Set<Class<?>> excludedClasses = includeRemoteViews
+ ImmutableSet<Class<?>> excludedClasses = includeRemoteViews
? ImmutableSet.of()
: ImmutableSet.of(RemoteViews.class);
Location location = Location.root(Notification.Builder.class);
@@ -294,7 +301,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static Object generateObject(Class<?> clazz, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
if (excludingClasses.contains(clazz)) {
throw new IllegalArgumentException(
String.format("Asked to generate a %s but it's part of the excluded set (%s)",
@@ -369,7 +376,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static Object constructEmpty(Class<?> clazz, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
Constructor<?> bestConstructor;
if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) {
// Use the preferred constructor.
@@ -431,7 +438,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static void invokeAllSetters(Object instance, Location where, boolean allOverloads,
- boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes,
+ boolean includingVoidMethods, ImmutableSet<Class<?>> excludingParameterTypes,
SpecialParameterGenerator specialGenerator) {
for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where,
allOverloads, includingVoidMethods, excludingParameterTypes)) {
@@ -462,24 +469,34 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static Object[] generateParameters(Executable executable, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
+ " in " + where);
Type[] parameterTypes = executable.getGenericParameterTypes();
Object[] parameterValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
- parameterValues[i] = generateParameter(
- parameterTypes[i],
+ boolean generateUriAsString = false;
+ Type parameterType = parameterTypes[i];
+ if (SETTERS_WITH_STRING_AS_URI.contains(executable)
+ && parameterType.equals(String.class)) {
+ generateUriAsString = true;
+ }
+ Object value = generateParameter(
+ generateUriAsString ? Uri.class : parameterType,
where.plus(executable,
String.format("[%d,%s]", i, parameterTypes[i].getTypeName())),
excludingClasses,
specialGenerator);
+ if (generateUriAsString) {
+ value = ((Uri) value).toString();
+ }
+ parameterValues[i] = value;
}
return parameterValues;
}
private static Object generateParameter(Type parameterType, Location where,
- Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+ ImmutableSet<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
if (parameterType instanceof Class<?> parameterClass) {
return generateObject(
parameterClass,
@@ -487,7 +504,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
excludingClasses,
specialGenerator);
} else if (parameterType instanceof ParameterizedType parameterizedType) {
- if (parameterizedType.getRawType().equals(List.class)
+ if ((parameterizedType.getRawType().equals(List.class)
+ || parameterizedType.getRawType().equals(ArrayList.class))
&& parameterizedType.getActualTypeArguments()[0] instanceof Class<?>) {
ArrayList listValue = new ArrayList();
for (int i = 0; i < NUM_ELEMENTS_IN_ARRAY; i++) {
@@ -503,12 +521,14 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static class ReflectionUtils {
- static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
- return Arrays.stream(containerClass.getDeclaredClasses())
- .filter(
- innerClass -> clazz.isAssignableFrom(innerClass)
- && !Modifier.isAbstract(innerClass.getModifiers()))
- .collect(Collectors.toSet());
+ static ImmutableSet<Class<?>> getConcreteSubclasses(Class<?> clazz,
+ Class<?> containerClass) {
+ return ImmutableSet.copyOf(
+ Arrays.stream(containerClass.getDeclaredClasses())
+ .filter(
+ innerClass -> clazz.isAssignableFrom(innerClass)
+ && !Modifier.isAbstract(innerClass.getModifiers()))
+ .collect(Collectors.toSet()));
}
static String methodToString(Executable executable) {
@@ -611,9 +631,16 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
private static class SpecialParameterGenerator {
+
+ private static final ImmutableSet<Class<?>> INTERESTING_CLASSES_WITH_SPECIAL_GENERATION =
+ ImmutableSet.of(Uri.class, Icon.class, Intent.class, PendingIntent.class,
+ RemoteViews.class);
+
private static final ImmutableSet<Class<?>> INTERESTING_CLASSES =
- ImmutableSet.of(Person.class, Uri.class, Icon.class, Intent.class,
- PendingIntent.class, RemoteViews.class);
+ new ImmutableSet.Builder<Class<?>>()
+ .addAll(INTERESTING_CLASSES_WITH_SPECIAL_GENERATION)
+ .add(Person.class) // Constructed via reflection, but high-score.
+ .build();
private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of();
private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES =
@@ -637,7 +664,7 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
}
static boolean canGenerate(Class<?> clazz) {
- return INTERESTING_CLASSES.contains(clazz)
+ return INTERESTING_CLASSES_WITH_SPECIAL_GENERATION.contains(clazz)
|| MOCKED_CLASSES.contains(clazz)
|| clazz.equals(Context.class)
|| clazz.equals(Bundle.class)
@@ -672,17 +699,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
return Icon.createWithContentUri(iconUri);
}
- if (clazz == Person.class) {
- // TODO(b/310189261): Person.setUri takes a string instead of a URI. We should
- // find a way to use the SpecialParameterGenerator instead of this custom one.
- Uri personUri = generateUri(
- where.plus(Person.Builder.class).plus("setUri", String.class));
- Uri iconUri = generateUri(where.plus(Person.Builder.class).plus("setIcon",
- Icon.class).plus(Icon.class).plus("createWithContentUri", Uri.class));
- return new Person.Builder().setUri(personUri.toString()).setIcon(
- Icon.createWithContentUri(iconUri)).setName("John Doe").build();
- }
-
if (clazz == Intent.class) {
return new Intent("action", generateUri(where.plus(Intent.class)));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1c57623442c5..29faed195b4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -132,8 +132,10 @@ import org.junit.runner.RunWith;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -563,6 +565,86 @@ public class ActivityStarterTests extends WindowTestsBase {
return Pair.create(splitPrimaryActivity, splitSecondActivity);
}
+ /**
+ * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+ * while it is already on top, reports it as delivering to top.
+ */
+ @Test
+ public void testDesktopModeDeliverToTop() {
+ final ActivityStarter starter = prepareStarter(
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP,
+ false /* mockGetRootTask */);
+ final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+
+ // Set focus back to the first task.
+ activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop");
+
+ // Start activity and delivered new intent.
+ starter.getIntent().setComponent(activities.get(3).mActivityComponent);
+ doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any());
+ final int result = starter.setReason("testDesktopModeDeliverToTop").execute();
+
+ // Ensure result is delivering intent to top.
+ assertEquals(START_DELIVERED_TO_TOP, result);
+ }
+
+ /**
+ * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+ * reports it is brought to front instead of delivering to top.
+ */
+ @Test
+ public void testDesktopModeTaskToFront() {
+ final ActivityStarter starter = prepareStarter(
+ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
+ final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+ final ActivityRecord desktopModeFocusActivity = activities.get(0);
+ final ActivityRecord desktopModeReusableActivity = activities.get(1);
+ final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setParentTask(desktopModeReusableActivity.getRootTask()).build();
+ assertTrue(desktopModeTopActivity.inMultiWindowMode());
+
+ // Let first stack has focus.
+ desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront");
+
+ // Start activity and delivered new intent.
+ starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent);
+ doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+ final int result = starter.setReason("testDesktopModeMoveToFront").execute();
+
+ // Ensure result is moving task to front.
+ assertEquals(START_TASK_TO_FRONT, result);
+ }
+
+ /** Returns 4 activities. */
+ private List<ActivityRecord> createActivitiesInDesktopMode() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+
+ for (int i = 0; i < 4; i++) {
+ Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+ bounds.offset(20 * i, 20 * i);
+ desktopOrganizer.createTask(bounds);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ activityRecords.add(new TaskBuilder(mSupervisor)
+ .setParentTask(desktopOrganizer.mTasks.get(i))
+ .setCreateActivity(true)
+ .build()
+ .getTopMostActivity());
+ }
+
+ for (int i = 0; i < 4; i++) {
+ activityRecords.get(i).setVisibleRequested(true);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+ }
+
+ return activityRecords;
+ }
+
@Test
public void testMoveVisibleTaskToFront() {
final ActivityRecord activity = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 114b9c3a68f2..c7c791337bb4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,6 +20,7 @@ import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -133,7 +134,9 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/** Common base class for window manager unit test classes. */
class WindowTestsBase extends SystemServiceTestsBase {
@@ -1892,6 +1895,55 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
}
+ static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer {
+ final int mDesktopModeDefaultWidthDp = 840;
+ final int mDesktopModeDefaultHeightDp = 630;
+ final int mDesktopDensity = 284;
+
+ final ActivityTaskManagerService mService;
+ final TaskDisplayArea mDefaultTDA;
+ List<Task> mTasks;
+ final DisplayContent mDisplay;
+ Rect mStableBounds;
+
+ TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
+ mService = service;
+ mDefaultTDA = display.getDefaultTaskDisplayArea();
+ mDisplay = display;
+ mService.mTaskOrganizerController.registerTaskOrganizer(this);
+ mTasks = new ArrayList<>();
+ mStableBounds = display.getBounds();
+ }
+
+ TestDesktopOrganizer(ActivityTaskManagerService service) {
+ this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
+ }
+
+ public Task createTask(Rect bounds) {
+ Task task = mService.mTaskOrganizerController.createRootTask(
+ mDisplay, WINDOWING_MODE_FREEFORM, null);
+ task.setBounds(bounds);
+ mTasks.add(task);
+ spyOn(task);
+ return task;
+ }
+
+ public Rect getDefaultDesktopTaskBounds() {
+ int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f);
+ int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f);
+ Rect outBounds = new Rect();
+
+ outBounds.set(0, 0, width, height);
+ // Center the task in stable bounds
+ outBounds.offset(
+ mStableBounds.centerX() - outBounds.centerX(),
+ mStableBounds.centerY() - outBounds.centerY()
+ );
+ return outBounds;
+ }
+
+ }
+
static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
return createTestWindowToken(type, dc, false /* persistOnEmpty */);
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 94c737d61b0a..a089f5c9d641 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -18,6 +18,7 @@ package android.telecom;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -30,11 +31,15 @@ import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Represents a distinct method to place or receive a phone call. Apps which can place calls and
@@ -491,6 +496,7 @@ public final class PhoneAccount implements Parcelable {
private final Bundle mExtras;
private boolean mIsEnabled;
private String mGroupId;
+ private final Set<PhoneAccountHandle> mSimultaneousCallingRestriction;
@Override
public boolean equals(Object o) {
@@ -508,7 +514,9 @@ public final class PhoneAccount implements Parcelable {
Objects.equals(mShortDescription, that.mShortDescription) &&
Objects.equals(mSupportedUriSchemes, that.mSupportedUriSchemes) &&
areBundlesEqual(mExtras, that.mExtras) &&
- Objects.equals(mGroupId, that.mGroupId);
+ Objects.equals(mGroupId, that.mGroupId)
+ && Objects.equals(mSimultaneousCallingRestriction,
+ that.mSimultaneousCallingRestriction);
}
@Override
@@ -516,7 +524,7 @@ public final class PhoneAccount implements Parcelable {
return Objects.hash(mAccountHandle, mAddress, mSubscriptionAddress, mCapabilities,
mHighlightColor, mLabel, mShortDescription, mSupportedUriSchemes,
mSupportedAudioRoutes,
- mExtras, mIsEnabled, mGroupId);
+ mExtras, mIsEnabled, mGroupId, mSimultaneousCallingRestriction);
}
/**
@@ -537,6 +545,7 @@ public final class PhoneAccount implements Parcelable {
private Bundle mExtras;
private boolean mIsEnabled = false;
private String mGroupId = "";
+ private Set<PhoneAccountHandle> mSimultaneousCallingRestriction = null;
/**
* Creates a builder with the specified {@link PhoneAccountHandle} and label.
@@ -787,6 +796,57 @@ public final class PhoneAccount implements Parcelable {
}
/**
+ * Restricts the ability of this {@link PhoneAccount} to ONLY support simultaneous calling
+ * with the other {@link PhoneAccountHandle}s in this Set.
+ * <p>
+ * If two or more {@link PhoneAccount}s support calling simultaneously, it means that
+ * Telecom allows the user to place additional outgoing calls and receive additional
+ * incoming calls using other {@link PhoneAccount}s while this PhoneAccount also has one or
+ * more active calls.
+ * <p>
+ * If this setter method is never called or cleared using
+ * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
+ * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+ * <p>
+ * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
+ * were registered by the same application. Simultaneous calling across applications is
+ * always possible as long as the {@link Connection} supports hold. If a
+ * {@link PhoneAccountHandle} is included here and the package name doesn't match this
+ * application's package name, {@link TelecomManager#registerPhoneAccount(PhoneAccount)}
+ * will throw a {@link SecurityException}.
+ *
+ * @param handles The other {@link PhoneAccountHandle}s that support calling simultaneously
+ * with this one. Use {@link #clearSimultaneousCallingRestriction()} to remove the
+ * restriction and allow simultaneous calling to be supported across all
+ * {@link PhoneAccount}s registered by this package.
+ * @return The Builder used to set up the new PhoneAccount.
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public @NonNull Builder setSimultaneousCallingRestriction(
+ @NonNull Set<PhoneAccountHandle> handles) {
+ if (handles == null) {
+ throw new IllegalArgumentException("the Set of PhoneAccountHandles must not be "
+ + "null");
+ }
+ mSimultaneousCallingRestriction = handles;
+ return this;
+ }
+
+ /**
+ * Clears a previously set simultaneous calling restriction set when
+ * {@link PhoneAccount.Builder#Builder(PhoneAccount)} is used to create a new PhoneAccount
+ * from an existing one.
+ *
+ * @return The Builder used to set up the new PhoneAccount.
+ * @see #setSimultaneousCallingRestriction(Set)
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public @NonNull Builder clearSimultaneousCallingRestriction() {
+ mSimultaneousCallingRestriction = null;
+ return this;
+ }
+
+ /**
* Creates an instance of a {@link PhoneAccount} based on the current builder settings.
*
* @return The {@link PhoneAccount}.
@@ -810,7 +870,8 @@ public final class PhoneAccount implements Parcelable {
mExtras,
mSupportedAudioRoutes,
mIsEnabled,
- mGroupId);
+ mGroupId,
+ mSimultaneousCallingRestriction);
}
}
@@ -827,7 +888,8 @@ public final class PhoneAccount implements Parcelable {
Bundle extras,
int supportedAudioRoutes,
boolean isEnabled,
- String groupId) {
+ String groupId,
+ Set<PhoneAccountHandle> simultaneousCallingRestriction) {
mAccountHandle = account;
mAddress = address;
mSubscriptionAddress = subscriptionAddress;
@@ -841,6 +903,7 @@ public final class PhoneAccount implements Parcelable {
mSupportedAudioRoutes = supportedAudioRoutes;
mIsEnabled = isEnabled;
mGroupId = groupId;
+ mSimultaneousCallingRestriction = simultaneousCallingRestriction;
}
public static Builder builder(
@@ -1050,6 +1113,49 @@ public final class PhoneAccount implements Parcelable {
return (mCapabilities & CAPABILITY_SELF_MANAGED) == CAPABILITY_SELF_MANAGED;
}
+ /**
+ * If a restriction is set (see {@link #hasSimultaneousCallingRestriction()}), this method
+ * returns the Set of {@link PhoneAccountHandle}s that are allowed to support calls
+ * simultaneously with this {@link PhoneAccount}.
+ * <p>
+ * If this {@link PhoneAccount} is busy with one or more ongoing calls, a restriction is set on
+ * this PhoneAccount (see {@link #hasSimultaneousCallingRestriction()} to check), and a new
+ * incoming or outgoing call is received or placed on a PhoneAccount that is not in this Set,
+ * Telecom will reject or cancel the pending call in favor of keeping the ongoing call alive.
+ * <p>
+ * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
+ * were registered by the same application. Simultaneous calling across applications is
+ * always possible as long as the {@link Connection} supports hold.
+ *
+ * @return the Set of {@link PhoneAccountHandle}s that this {@link PhoneAccount} supports
+ * simultaneous calls with.
+ * @throws IllegalStateException If there is no restriction set on this {@link PhoneAccount}
+ * and this method is called. Whether or not there is a restriction can be checked using
+ * {@link #hasSimultaneousCallingRestriction()}.
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public @NonNull Set<PhoneAccountHandle> getSimultaneousCallingRestriction() {
+ if (mSimultaneousCallingRestriction == null) {
+ throw new IllegalStateException("This method can not be called if there is no "
+ + "simultaneous calling restriction. See #hasSimultaneousCallingRestriction");
+ }
+ return mSimultaneousCallingRestriction;
+ }
+
+ /**
+ * Whether or not this {@link PhoneAccount} contains a simultaneous calling restriction on it.
+ *
+ * @return {@code true} if this PhoneAccount contains a simultaneous calling restriction,
+ * {@code false} if it does not. Use {@link #getSimultaneousCallingRestriction()} to query which
+ * other {@link PhoneAccount}s support simultaneous calling with this one.
+ * @see #getSimultaneousCallingRestriction() for more information on how the sinultaneous
+ * calling restriction works.
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public boolean hasSimultaneousCallingRestriction() {
+ return mSimultaneousCallingRestriction != null;
+ }
+
//
// Parcelable implementation
//
@@ -1095,6 +1201,12 @@ public final class PhoneAccount implements Parcelable {
out.writeBundle(mExtras);
out.writeString(mGroupId);
out.writeInt(mSupportedAudioRoutes);
+ if (mSimultaneousCallingRestriction == null) {
+ out.writeBoolean(false);
+ } else {
+ out.writeBoolean(true);
+ out.writeTypedList(mSimultaneousCallingRestriction.stream().toList());
+ }
}
public static final @android.annotation.NonNull Creator<PhoneAccount> CREATOR
@@ -1140,6 +1252,13 @@ public final class PhoneAccount implements Parcelable {
mExtras = in.readBundle();
mGroupId = in.readString();
mSupportedAudioRoutes = in.readInt();
+ if (in.readBoolean()) {
+ List<PhoneAccountHandle> list = new ArrayList<>();
+ in.readTypedList(list, PhoneAccountHandle.CREATOR);
+ mSimultaneousCallingRestriction = new ArraySet<>(list);
+ } else {
+ mSimultaneousCallingRestriction = null;
+ }
}
@Override
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ec5f7a77b2c..67bb1f03cad6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6888,6 +6888,7 @@ public class TelephonyManager {
}
}
+ // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
/**
* @return true if the current device is "voice capable".
* <p>
@@ -6901,7 +6902,10 @@ public class TelephonyManager {
* PackageManager.FEATURE_TELEPHONY system feature, which is available
* on any device with a telephony radio, even if the device is
* data-only.
- * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
+ * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice
+ * capability may also be overridden by carriers for a given subscription. For voice capable
+ * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
+ * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@Deprecated
@@ -6923,9 +6927,10 @@ public class TelephonyManager {
* .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
* radio, even if the device is data-only.
* <p>
- * To check if a subscription is "voice capable", call method
- * {@link SubscriptionInfo#getServiceCapabilities()} and compare the result with
- * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+ * Starting from Android 15, voice capability may also be overridden by carrier for a given
+ * subscription on a voice capable device. To check if a subscription is "voice capable",
+ * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
*
* @see SubscriptionInfo#getServiceCapabilities()
*/
@@ -6943,7 +6948,10 @@ public class TelephonyManager {
* <p>
* Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
* disabled when device doesn't support sms.
- * @deprecated Replaced by {@link #isDeviceSmsCapable()}
+ * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS
+ * capability may also be overridden by carriers for a given subscription. For SMS capable
+ * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
+ * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
*/
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
public boolean isSmsCapable() {
@@ -6961,9 +6969,10 @@ public class TelephonyManager {
* Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
* disabled when device doesn't support SMS.
* <p>
- * To check if a subscription is "SMS capable", call method
- * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
- * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+ * Starting from Android 15, SMS capability may also be overridden by carriers for a given
+ * subscription on an SMS capable device. To check if a subscription is "SMS capable",
+ * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+ * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
*
* @see SubscriptionInfo#getServiceCapabilities()
*/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index acbf354bb4de..24296f4d9031 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3078,6 +3078,16 @@ interface ITelephony {
in List<String> satelliteCountryCodes);
/**
+ * This API can be used in only testing to override oem-enabled satellite provision status.
+ *
+ * @param reset {@code true} mean the overriding status should not be used, {@code false}
+ * otherwise.
+ * @param isProvisioned The overriding provision status.
+ * @return {@code true} if the provision status is set successfully, {@code false} otherwise.
+ */
+ boolean setOemEnabledSatelliteProvisionStatus(in boolean reset, in boolean isProvisioned);
+
+ /**
* Test method to confirm the file contents are not altered.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index 2bc056ee743f..cee27b6847ec 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -16,6 +16,8 @@
package android.transparency.test.app;
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -27,6 +29,7 @@ import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.internal.os.IBinaryTransparencyService.AppInfo;
import org.junit.Before;
@@ -116,7 +119,12 @@ public class BinaryTransparencyTest {
@Test
public void testCollectAllSilentInstalledMbaInfo() {
// Action
- var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
+ var appInfoList =
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mBt,
+ (Bt) ->
+ mBt.collectAllSilentInstalledMbaInfo(new Bundle()),
+ GET_BACKGROUND_INSTALLED_PACKAGES);
// Verify
assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index b05863188beb..256a4696b763 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -292,14 +292,16 @@ class InputManagerServiceTests {
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
+ verify(native).setPointerIconVisibility(10, false)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
localService.setMousePointerAccelerationEnabled(false, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(false))
+ verify(native).setMousePointerAccelerationEnabled(10, false)
service.onDisplayRemoved(10)
+ verify(native).setPointerIconVisibility(10, true)
verify(native).displayRemoved(eq(10))
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
- verify(native).setMousePointerAccelerationEnabled(true)
+ verify(native).setMousePointerAccelerationEnabled(10, true)
verifyNoMoreInteractions(native)
// This call should not block because the virtual mouse pointer override was never removed.
@@ -315,25 +317,26 @@ class InputManagerServiceTests {
localService.setPointerIconVisible(false, 10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+ verify(native).setPointerIconVisibility(10, false)
localService.setMousePointerAccelerationEnabled(false, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(false))
+ verify(native).setMousePointerAccelerationEnabled(10, false)
localService.setPointerIconVisible(true, 10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+ verify(native).setPointerIconVisibility(10, true)
localService.setMousePointerAccelerationEnabled(true, 10)
- verify(native).setMousePointerAccelerationEnabled(eq(true))
+ verify(native).setMousePointerAccelerationEnabled(10, true)
- // Verify that setting properties on a different display is not propagated until the
- // pointer is moved to that display.
localService.setPointerIconVisible(false, 20)
+ verify(native).setPointerIconVisibility(20, false)
localService.setMousePointerAccelerationEnabled(false, 20)
+ verify(native).setMousePointerAccelerationEnabled(20, false)
verifyNoMoreInteractions(native)
clearInvocations(native)
setVirtualMousePointerDisplayIdAndVerify(20)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
- verify(native).setMousePointerAccelerationEnabled(eq(false))
}
@Test
@@ -341,12 +344,13 @@ class InputManagerServiceTests {
localService.setPointerIconVisible(false, 10)
localService.setMousePointerAccelerationEnabled(false, 10)
+ verify(native).setPointerIconVisibility(10, false)
+ verify(native).setMousePointerAccelerationEnabled(10, false)
verifyNoMoreInteractions(native)
setVirtualMousePointerDisplayIdAndVerify(10)
verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
- verify(native).setMousePointerAccelerationEnabled(eq(false))
}
@Test
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index cbdcb8869628..518183f9cd64 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -30,6 +30,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.UserManager;
@@ -146,7 +147,8 @@ public class RollbackTest {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
// Upgrade from v1 to v2, with rollbacks enabled.
- Install.single(TestApp.A2).setEnableRollback().commit();
+ Install.single(TestApp.A2).setEnableRollback().setRollbackImpactLevel(
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
// The app should now be available for rollback.
@@ -154,6 +156,8 @@ public class RollbackTest {
assertThat(available).isNotStaged();
assertThat(available).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(available.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
// We should not have received any rollback requests yet.
// TODO: Possibly flaky if, by chance, some other app on device
@@ -264,6 +268,8 @@ public class RollbackTest {
RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B);
assertThat(rollbackB).packagesContainsExactly(
Rollback.from(TestApp.B2).to(TestApp.B1));
+ assertThat(rollbackB.getRollbackImpactLevel()).isEqualTo(
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
// Register rollback committed receiver
RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();