summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--ProtoLibraries.bp1
-rw-r--r--STABILITY_OWNERS2
-rw-r--r--TEST_MAPPING3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java7
-rw-r--r--api/StubLibraries.bp11
-rw-r--r--core/api/current.txt8
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt53
-rw-r--r--core/api/test-lint-baseline.txt6
-rw-r--r--core/java/android/app/Activity.java44
-rw-r--r--core/java/android/app/ActivityOptions.aidl20
-rw-r--r--core/java/android/app/ActivityOptions.java249
-rw-r--r--core/java/android/app/ActivityThread.java42
-rw-r--r--core/java/android/app/ActivityTransitionState.java50
-rw-r--r--core/java/android/app/ApplicationPackageManager.java11
-rw-r--r--core/java/android/app/AutomaticZenRule.java98
-rw-r--r--core/java/android/app/ClientTransactionHandler.java3
-rw-r--r--core/java/android/app/ForegroundServiceTypePolicy.java23
-rw-r--r--core/java/android/app/IApplicationThread.aidl3
-rw-r--r--core/java/android/app/LocalActivityManager.java2
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java27
-rw-r--r--core/java/android/app/servertransaction/StartActivityItem.java22
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java2
-rw-r--r--core/java/android/companion/AssociationInfo.java36
-rw-r--r--core/java/android/content/Intent.java29
-rw-r--r--core/java/android/content/pm/ActivityInfo.java5
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java12
-rw-r--r--core/java/android/content/pm/ModuleInfo.java8
-rw-r--r--core/java/android/content/pm/PackageInstaller.java14
-rw-r--r--core/java/android/content/pm/PackageManager.java11
-rw-r--r--core/java/android/content/pm/PermissionInfo.java3
-rw-r--r--core/java/android/content/pm/ServiceInfo.java15
-rw-r--r--core/java/android/content/pm/flags.aconfig15
-rw-r--r--core/java/android/content/res/Element.java12
-rw-r--r--core/java/android/credentials/CredentialManager.java6
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java16
-rw-r--r--core/java/android/database/sqlite/flags.aconfig8
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java7
-rw-r--r--core/java/android/net/Uri.java49
-rwxr-xr-xcore/java/android/os/Build.java4
-rw-r--r--core/java/android/os/ISystemConfig.aidl5
-rw-r--r--core/java/android/os/OWNERS6
-rw-r--r--core/java/android/os/RecoverySystem.java71
-rw-r--r--core/java/android/os/SystemConfigManager.java14
-rw-r--r--core/java/android/os/VintfObject.java43
-rw-r--r--core/java/android/permission/flags.aconfig7
-rw-r--r--core/java/android/provider/Settings.java76
-rw-r--r--core/java/android/service/notification/ZenDeviceEffects.java159
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java35
-rw-r--r--core/java/android/service/notification/ZenModeDiff.java5
-rw-r--r--core/java/android/service/notification/ZenPolicy.java328
-rw-r--r--core/java/android/view/DisplayAddress.java24
-rw-r--r--core/java/android/view/ViewRootImpl.java6
-rw-r--r--core/java/android/view/WindowManager.java35
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java6
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java43
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java10
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java64
-rw-r--r--core/java/com/android/server/OWNERS1
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp53
-rw-r--r--core/jni/android_os_VintfObject.cpp12
-rw-r--r--core/jni/hwbinder/EphemeralStorage.cpp2
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/values/attrs_manifest.xml10
-rw-r--r--core/res/res/values/colors.xml4
-rw-r--r--core/res/res/values/config_telephony.xml30
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/res/res/values/themes.xml2
-rw-r--r--core/tests/coretests/src/android/app/AutomaticZenRuleTest.java66
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java3
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TestUtils.java6
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java2
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java54
-rw-r--r--data/etc/com.android.systemui.xml1
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--data/keyboards/Android.bp29
-rw-r--r--data/keyboards/Android.mk44
-rw-r--r--data/keyboards/Vendor_0957_Product_0031.kl82
-rw-r--r--data/keyboards/common.mk22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java12
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java3
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java90
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt69
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java148
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt49
-rw-r--r--location/api/current.txt2
-rw-r--r--location/java/android/location/GnssNavigationMessage.java2
-rw-r--r--media/java/android/media/MediaRoute2Info.java4
-rw-r--r--media/java/android/media/session/PlaybackState.java48
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt14
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt3
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt20
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java64
-rw-r--r--packages/SettingsLib/LintChecker/Android.bp33
-rw-r--r--packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt146
-rw-r--r--packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt28
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/Android.bp4
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/aconfig/predictive_back.aconfig7
-rw-r--r--packages/SystemUI/animation/Android.bp2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt2
-rw-r--r--packages/SystemUI/compose/core/Android.bp2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt217
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt2
-rw-r--r--packages/SystemUI/customization/Android.bp2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt)70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt200
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt107
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt10
-rw-r--r--packages/SystemUI/plugin/Android.bp2
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml2
-rw-r--r--packages/SystemUI/res/layout/remote_input.xml1
-rw-r--r--packages/SystemUI/res/values/config.xml15
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/shared/Android.bp2
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt7
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java (renamed from packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java)6
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt201
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/BindableIcon.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/ModernStatusBarViewCreator.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt268
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt391
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt322
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt49
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt15
-rw-r--r--packages/overlays/NoCutoutOverlay/res/values/config.xml7
-rw-r--r--proto/src/criticalevents/critical_event_log.proto3
-rw-r--r--services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java2
-rw-r--r--services/companion/java/com/android/server/companion/BackupRestoreProcessor.java40
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java32
-rw-r--r--services/companion/java/com/android/server/companion/PersistentDataStore.java16
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java18
-rw-r--r--services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java4
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/BootReceiver.java83
-rw-r--r--services/core/java/com/android/server/OWNERS3
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java188
-rw-r--r--services/core/java/com/android/server/SystemConfig.java19
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java51
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java89
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java2
-rw-r--r--services/core/java/com/android/server/criticalevents/CriticalEventLog.java8
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java29
-rw-r--r--services/core/java/com/android/server/display/DisplayBrightnessState.java30
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java21
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java5
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java5
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessReason.java10
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java2
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java24
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java176
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java137
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java13
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java45
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java10
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/display/layout/Layout.java11
-rw-r--r--services/core/java/com/android/server/inputmethod/ClientController.java162
-rw-r--r--services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java21
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java215
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java14
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java7
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java11
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java320
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryManager.java13
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java22
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java329
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java135
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java60
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java43
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java49
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java33
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java8
-rw-r--r--services/core/java/com/android/server/policy/Android.bp10
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java19
-rw-r--r--services/core/java/com/android/server/policy/WindowWakeUpPolicy.java121
-rw-r--r--services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java75
-rw-r--r--services/core/java/com/android/server/policy/window_policy_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/trust/TEST_MAPPING13
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java13
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperData.java22
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java15
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java56
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java18
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java30
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java15
-rw-r--r--services/core/java/com/android/server/wm/SensitiveContentPackages.java83
-rw-r--r--services/core/java/com/android/server/wm/Task.java22
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java25
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java27
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java8
-rw-r--r--services/core/jni/Android.bp11
-rw-r--r--services/core/jni/OWNERS4
-rw-r--r--services/core/jni/com_android_server_BootReceiver.cpp57
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp6
-rw-r--r--services/core/jni/onload.cpp6
-rw-r--r--services/core/xsd/display-layout-config/display-layout-config.xsd9
-rw-r--r--services/core/xsd/display-layout-config/schema/current.txt6
-rw-r--r--services/java/com/android/server/SystemConfigService.java11
-rw-r--r--services/java/com/android/server/SystemServer.java6
-rw-r--r--services/robotests/Android.bp4
-rw-r--r--services/robotests/backup/Android.bp4
-rw-r--r--services/robotests/backup/config/robolectric.properties4
-rw-r--r--services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java1
-rw-r--r--services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java27
-rw-r--r--services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java2
-rw-r--r--services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java13
-rw-r--r--services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java1
-rw-r--r--services/tests/InputMethodSystemServerTests/Android.bp24
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java97
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java60
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java60
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt40
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java9
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java22
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt91
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java381
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java316
-rw-r--r--services/tests/servicestests/Android.bp10
-rw-r--r--services/tests/servicestests/src/com/android/server/BootReceiverTest.java97
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java189
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java35
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java16
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java79
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java500
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java354
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java104
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java45
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java31
-rw-r--r--telephony/java/android/telephony/SecurityAlgorithmUpdate.java20
-rw-r--r--telephony/java/android/telephony/euicc/EuiccCardManager.java72
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java57
-rw-r--r--telephony/java/android/telephony/ims/ImsMmTelManager.java38
-rw-r--r--telephony/java/android/telephony/ims/ImsRcsManager.java16
-rw-r--r--telephony/java/android/telephony/ims/ProvisioningManager.java58
-rw-r--r--telephony/java/android/telephony/ims/RcsUceAdapter.java17
-rw-r--r--telephony/java/android/telephony/ims/SipDelegateManager.java5
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl23
-rw-r--r--tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java17
417 files changed, 11853 insertions, 3057 deletions
diff --git a/Android.bp b/Android.bp
index b139b7e50676..82b844b3d0a4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -218,6 +218,7 @@ java_library {
"apex_aidl_interface-java",
"packagemanager_aidl-java",
"framework-protos",
+ "libtombstone_proto_java",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf203334e..d03bbd249b00 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,7 +34,6 @@ gensrcs {
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
- ":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
new file mode 100644
index 000000000000..a7ecb4dfdd44
--- /dev/null
+++ b/STABILITY_OWNERS
@@ -0,0 +1,2 @@
+gaillard@google.com
+
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 117faa203325..d59775f4060b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -132,6 +132,9 @@
},
{
"name": "vts_treble_vintf_vendor_test"
+ },
+ {
+ "name": "CtsStrictJavaPackagesTestCases"
}
],
"postsubmit-ravenwood": [
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5a32a02ca8ce..abf80089b985 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -115,6 +115,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
@@ -229,6 +230,9 @@ public class AlarmManagerService extends SystemService {
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
+ // System property read on some device configurations to initialize time properly.
+ private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
+
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2142,6 +2146,9 @@ public class AlarmManagerService extends SystemService {
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
+
+ final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
+ SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 28b2d4b5e3ee..ef1fa6097056 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -900,10 +900,19 @@ droidstubs {
],
api_levels_sdk_type: "system",
extensions_info_file: ":sdk-extensions-info",
+ dists: [
+ // Make the api-versions.xml file for the system API available in the
+ // sdk build target.
+ {
+ targets: ["sdk"],
+ dest: "api-versions_system.xml",
+ tag: ".api_versions.xml",
+ },
+ ],
}
// This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api_versions_module_lib/android_common/metalava/api-versions.xml
+// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
droidstubs {
name: "api_versions_module_lib",
srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
diff --git a/core/api/current.txt b/core/api/current.txt
index c7b921c8f6d5..e0b224e92a04 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -102,6 +102,7 @@ package android {
field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+ field @FlaggedApi("android.content.pm.introduce_media_processing_type") public static final String FOREGROUND_SERVICE_MEDIA_PROCESSING = "android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING";
field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
@@ -5315,6 +5316,7 @@ package android.app {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
+ method @FlaggedApi("android.app.modes_api") public boolean canUpdate();
method public int describeContents();
method public android.net.Uri getConditionId();
method @Nullable public android.content.ComponentName getConfigurationActivity();
@@ -12368,7 +12370,6 @@ package android.content.pm {
public final class ModuleInfo implements android.os.Parcelable {
method public int describeContents();
- method @FlaggedApi("android.content.pm.provide_info_of_apk_in_apex") @NonNull public java.util.Collection<java.lang.String> getApkInApexPackageNames();
method @Nullable public CharSequence getName();
method @Nullable public String getPackageName();
method public boolean isHidden();
@@ -12817,7 +12818,7 @@ package android.content.pm {
method public boolean isPackageSuspended();
method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
method public abstract boolean isSafeMode();
- method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
+ method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull java.io.File, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -13287,6 +13288,7 @@ package android.content.pm {
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+ field @FlaggedApi("android.content.pm.introduce_media_processing_type") @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 8192; // 0x2000
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
@@ -26291,6 +26293,7 @@ package android.media.session {
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
+ field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -53974,6 +53977,7 @@ package android.view {
field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+ field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
}
public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 494d48899c80..9077d02be8be 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -473,7 +473,7 @@ package android {
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_defaultNotes = 17039429; // 0x1040045
- field public static final int config_defaultRetailDemo;
+ field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo;
field public static final int config_defaultSms = 17039396; // 0x1040024
field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -4141,7 +4141,7 @@ package android.content.pm {
field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
- field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+ field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -10571,7 +10571,7 @@ package android.os {
method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static int rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
- method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
+ method @Deprecated public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME = 2000; // 0x7d0
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED = 3000; // 0xbb8
field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE = 5000; // 0x1388
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 572be192fb3e..d2af9db3714c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -284,6 +284,16 @@ package android.app {
method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
}
+ public final class AutomaticZenRule implements android.os.Parcelable {
+ method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
+ method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int);
+ }
+
public class BroadcastOptions extends android.app.ComponentOptions {
ctor public BroadcastOptions();
ctor public BroadcastOptions(@NonNull android.os.Bundle);
@@ -3007,6 +3017,49 @@ package android.service.notification {
method @Deprecated public boolean isBound();
}
+ @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+ method public int getUserModifiedFields();
+ field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4
+ field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10
+ field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20
+ field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40
+ field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80
+ field public static final int FIELD_GRAYSCALE = 1; // 0x1
+ field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200
+ field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100
+ field public static final int FIELD_NIGHT_MODE = 8; // 0x8
+ field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2
+ }
+
+ @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int);
+ }
+
+ public final class ZenPolicy implements android.os.Parcelable {
+ method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000
+ field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000
+ }
+
+ public static final class ZenPolicy.Builder {
+ ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
+ method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int);
+ }
+
}
package android.service.quickaccesswallet {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index bf26bd0a0ec6..5e904ef947c8 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -535,6 +535,10 @@ MissingNullability: android.widget.ImageView#isDefaultFocusHighlightNeeded(andro
Missing nullability on parameter `foreground` in method `isDefaultFocusHighlightNeeded`
+OptionalBuilderConstructorArgument: android.service.notification.ZenPolicy.Builder#Builder(android.service.notification.ZenPolicy) parameter #0:
+ Builder constructor arguments must be mandatory (i.e. not @Nullable): parameter policy in android.service.notification.ZenPolicy.Builder(android.service.notification.ZenPolicy policy)
+
+
ProtectedMember: android.app.AppDetailsActivity#onCreate(android.os.Bundle):
Protected methods not allowed; must be public: method android.app.AppDetailsActivity.onCreate(android.os.Bundle)}
ProtectedMember: android.view.ViewGroup#resetResolvedDrawables():
@@ -2143,6 +2147,8 @@ UnflaggedApi: android.service.notification.NotificationRankingUpdate#PARCELABLE_
New API must be flagged with @FlaggedApi: field android.service.notification.NotificationRankingUpdate.PARCELABLE_WRITE_RETURN_VALUE
UnflaggedApi: android.service.notification.NotificationRankingUpdate#isFdNotNullAndClosed():
New API must be flagged with @FlaggedApi: method android.service.notification.NotificationRankingUpdate.isFdNotNullAndClosed()
+UnflaggedApi: android.service.notification.ZenPolicy.Builder#Builder(android.service.notification.ZenPolicy):
+ New API must be flagged with @FlaggedApi: constructor android.service.notification.ZenPolicy.Builder(android.service.notification.ZenPolicy)
UnflaggedApi: android.telephony.TelephonyManager#HAL_SERVICE_SATELLITE:
New API must be flagged with @FlaggedApi: field android.telephony.TelephonyManager.HAL_SERVICE_SATELLITE
UnflaggedApi: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities:
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5d4d5e23d6db..f9583d22cf58 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -24,7 +24,9 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
+
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.AnimRes;
@@ -45,6 +47,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UiContext;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.VoiceInteractor.Request;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
@@ -930,8 +933,8 @@ public class Activity extends ContextThemeWrapper
@UnsupportedAppUsage
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
- /** The options for scene transition. */
- ActivityOptions mPendingOptions;
+ /** The scene transition info. */
+ SceneTransitionInfo mSceneTransitionInfo;
/** Whether this activity was launched from a bubble. **/
boolean mLaunchedFromBubble;
@@ -5807,10 +5810,9 @@ public class Activity extends ContextThemeWrapper
private Bundle transferSpringboardActivityOptions(@Nullable Bundle options) {
if (options == null && (mWindow != null && !mWindow.isActive())) {
- final ActivityOptions activityOptions = getActivityOptions();
- if (activityOptions != null &&
- activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- return activityOptions.toBundle();
+ final SceneTransitionInfo info = getSceneTransitionInfo();
+ if (info != null) {
+ return ActivityOptions.makeBasic().setSceneTransitionInfo(info).toBundle();
}
}
return options;
@@ -8079,8 +8081,10 @@ public class Activity extends ContextThemeWrapper
*
* @param callback the method to call when all visible activities behind this one have been
* drawn and it is safe to make this activity translucent again.
- * @param options activity options delivered to the activity below this one. The options
- * are retrieved using {@link #getActivityOptions}.
+ * @param options activity options that created from
+ * {@link ActivityOptions#makeSceneTransitionAnimation} which will be converted to
+ * {@link SceneTransitionInfo} and delivered to the activity below this one. The
+ * options are retrieved using {@link #getSceneTransitionInfo}.
* @return <code>true</code> if Window was opaque and will become translucent or
* <code>false</code> if window was translucent and no change needed to be made.
*
@@ -8116,27 +8120,27 @@ public class Activity extends ContextThemeWrapper
}
/** @hide */
- public void onNewActivityOptions(ActivityOptions options) {
- mActivityTransitionState.setEnterActivityOptions(this, options);
+ public void onNewSceneTransitionInfo(ActivityOptions.SceneTransitionInfo info) {
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, info);
if (!mStopped) {
mActivityTransitionState.enterReady(this);
}
}
/**
- * Takes the ActivityOptions passed in from the launching activity or passed back
+ * Takes the {@link SceneTransitionInfo} passed in from the launching activity or passed back
* from an activity launched by this activity in its call to {@link
* #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
*
- * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+ * @return The {@link SceneTransitionInfo} which based on the ActivityOptions that originally
+ * passed to {@link #convertToTranslucent}.
* @hide
*/
- @UnsupportedAppUsage
- ActivityOptions getActivityOptions() {
- final ActivityOptions options = mPendingOptions;
- // The option only applies once.
- mPendingOptions = null;
- return options;
+ SceneTransitionInfo getSceneTransitionInfo() {
+ final SceneTransitionInfo sceneTransitionInfo = mSceneTransitionInfo;
+ // The info only applies once.
+ mSceneTransitionInfo = null;
+ return sceneTransitionInfo;
}
/**
@@ -8780,7 +8784,7 @@ public class Activity extends ContextThemeWrapper
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
- mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
dispatchActivityPostCreated(icicle);
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -8798,7 +8802,7 @@ public class Activity extends ContextThemeWrapper
+ mComponent.getClassName());
}
dispatchActivityPreStarted();
- mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+ mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
diff --git a/core/java/android/app/ActivityOptions.aidl b/core/java/android/app/ActivityOptions.aidl
new file mode 100644
index 000000000000..bd5cd88959a3
--- /dev/null
+++ b/core/java/android/app/ActivityOptions.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** @hide */
+parcelable ActivityOptions.SceneTransitionInfo; \ No newline at end of file
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9c279c3b8254..8af7ed1bc8d3 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -357,22 +357,7 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
"android:activity.applyNoUserActionFlagForShortcut";
- /**
- * For Activity transitions, the calling Activity's TransitionListener used to
- * notify the called Activity when the shared element and the exit transitions
- * complete.
- */
- private static final String KEY_TRANSITION_COMPLETE_LISTENER
- = "android:activity.transitionCompleteListener";
-
- private static final String KEY_TRANSITION_IS_RETURNING
- = "android:activity.transitionIsReturning";
- private static final String KEY_TRANSITION_SHARED_ELEMENTS
- = "android:activity.sharedElementNames";
- private static final String KEY_RESULT_DATA = "android:activity.resultData";
- private static final String KEY_RESULT_CODE = "android:activity.resultCode";
- private static final String KEY_EXIT_COORDINATOR_INDEX
- = "android:activity.exitCoordinatorIndex";
+ private static final String KEY_SCENE_TRANSITION_INFO = "android:activity.sceneTransitionInfo";
/** See {@link SourceInfo}. */
private static final String KEY_SOURCE_INFO = "android.activity.sourceInfo";
@@ -472,12 +457,7 @@ public class ActivityOptions extends ComponentOptions {
private int mHeight;
private IRemoteCallback mAnimationStartedListener;
private IRemoteCallback mAnimationFinishedListener;
- private ResultReceiver mTransitionReceiver;
- private boolean mIsReturning;
- private ArrayList<String> mSharedElementNames;
- private Intent mResultData;
- private int mResultCode;
- private int mExitCoordinatorIndex;
+ private SceneTransitionInfo mSceneTransitionInfo;
private PendingIntent mUsageTimeReport;
private int mLaunchDisplayId = INVALID_DISPLAY;
private int mCallerDisplayId = INVALID_DISPLAY;
@@ -1006,8 +986,11 @@ public class ActivityOptions extends ComponentOptions {
ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener,
activity.getWindow(), opts, sharedElements);
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+ final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+ if (info != null) {
+ info.setExitCoordinatorKey(
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit));
+ }
return opts;
}
@@ -1029,13 +1012,16 @@ public class ActivityOptions extends ComponentOptions {
ActivityOptions opts = new ActivityOptions();
ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
exitCallbacks, callback, window, opts, sharedElements);
- opts.mExitCoordinatorIndex = -1;
+ final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+ if (info != null) {
+ info.setExitCoordinatorKey(-1);
+ }
return Pair.create(opts, exit);
}
/**
- * This method should be called when the
- * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, Pair[])}
+ * This method should be called when the {@link #startSharedElementAnimation(Window,
+ * ExitTransitionCallbacks, SharedElementCallback, Pair[])}
* animation must be stopped and the Views reset. This can happen if there was an error
* from startActivity or a springboard activity and the animation should stop and reset.
*
@@ -1088,9 +1074,11 @@ public class ActivityOptions extends ComponentOptions {
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window,
callback, names, names, views, false);
- opts.mTransitionReceiver = exit;
- opts.mSharedElementNames = names;
- opts.mIsReturning = false;
+ final SceneTransitionInfo info = new SceneTransitionInfo();
+ info.setResultReceiver(exit);
+ info.setSharedElementNames(names);
+ info.setReturning(false);
+ opts.setSceneTransitionInfo(info);
return exit;
}
@@ -1111,17 +1099,20 @@ public class ActivityOptions extends ComponentOptions {
int resultCode, Intent resultData) {
ActivityOptions opts = new ActivityOptions();
opts.mAnimationType = ANIM_SCENE_TRANSITION;
- opts.mSharedElementNames = sharedElementNames;
- opts.mTransitionReceiver = exitCoordinator;
- opts.mIsReturning = true;
- opts.mResultCode = resultCode;
- opts.mResultData = resultData;
+ final SceneTransitionInfo info = new SceneTransitionInfo();
+ info.setSharedElementNames(sharedElementNames);
+ info.setResultReceiver(exitCoordinator);
+ info.setReturning(true);
+ info.setResultCode(resultCode);
+ info.setResultData(resultData);
if (activity == null) {
- opts.mExitCoordinatorIndex = -1;
+ info.setExitCoordinatorKey(-1);
} else {
- opts.mExitCoordinatorIndex =
- activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+ info.setExitCoordinatorKey(
+ activity.mActivityTransitionState.addExitTransitionCoordinator(
+ exitCoordinator));
}
+ opts.setSceneTransitionInfo(info);
return opts;
}
@@ -1269,12 +1260,8 @@ public class ActivityOptions extends ComponentOptions {
break;
case ANIM_SCENE_TRANSITION:
- mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER, android.os.ResultReceiver.class);
- mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
- mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
- mResultData = opts.getParcelable(KEY_RESULT_DATA, android.content.Intent.class);
- mResultCode = opts.getInt(KEY_RESULT_CODE);
- mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
+ mSceneTransitionInfo = opts.getParcelable(KEY_SCENE_TRANSITION_INFO,
+ SceneTransitionInfo.class);
break;
}
mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
@@ -1437,9 +1424,6 @@ public class ActivityOptions extends ComponentOptions {
}
/** @hide */
- public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
-
- /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -1450,35 +1434,17 @@ public class ActivityOptions extends ComponentOptions {
}
/** @hide */
- public boolean isReturning() {
- return mIsReturning;
- }
-
- /**
- * Returns whether or not the ActivityOptions was created with
- * {@link #startSharedElementAnimation(Window, Pair[])}.
- *
- * @hide
- */
- boolean isCrossTask() {
- return mExitCoordinatorIndex < 0;
+ public ActivityOptions setSceneTransitionInfo(SceneTransitionInfo info) {
+ mSceneTransitionInfo = info;
+ return this;
}
/** @hide */
- public ArrayList<String> getSharedElementNames() {
- return mSharedElementNames;
+ public SceneTransitionInfo getSceneTransitionInfo() {
+ return mSceneTransitionInfo;
}
/** @hide */
- public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
-
- /** @hide */
- public int getResultCode() { return mResultCode; }
-
- /** @hide */
- public Intent getResultData() { return mResultData; }
-
- /** @hide */
public PendingIntent getUsageTimeReport() {
return mUsageTimeReport;
}
@@ -2102,12 +2068,7 @@ public class ActivityOptions extends ComponentOptions {
mPackageName = otherOptions.mPackageName;
}
mUsageTimeReport = otherOptions.mUsageTimeReport;
- mTransitionReceiver = null;
- mSharedElementNames = null;
- mIsReturning = false;
- mResultData = null;
- mResultCode = 0;
- mExitCoordinatorIndex = 0;
+ mSceneTransitionInfo = null;
mAnimationType = otherOptions.mAnimationType;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
@@ -2157,14 +2118,9 @@ public class ActivityOptions extends ComponentOptions {
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
break;
case ANIM_SCENE_TRANSITION:
- mTransitionReceiver = otherOptions.mTransitionReceiver;
- mSharedElementNames = otherOptions.mSharedElementNames;
- mIsReturning = otherOptions.mIsReturning;
+ mSceneTransitionInfo = otherOptions.mSceneTransitionInfo;
mThumbnail = null;
mAnimationStartedListener = null;
- mResultData = otherOptions.mResultData;
- mResultCode = otherOptions.mResultCode;
- mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
mLockTaskMode = otherOptions.mLockTaskMode;
@@ -2240,14 +2196,9 @@ public class ActivityOptions extends ComponentOptions {
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCENE_TRANSITION:
- if (mTransitionReceiver != null) {
- b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
+ if (mSceneTransitionInfo != null) {
+ b.putParcelable(KEY_SCENE_TRANSITION_INFO, mSceneTransitionInfo);
}
- b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
- b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
- b.putParcelable(KEY_RESULT_DATA, mResultData);
- b.putInt(KEY_RESULT_CODE, mResultCode);
- b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
if (mLockTaskMode) {
@@ -2607,4 +2558,124 @@ public class ActivityOptions extends ComponentOptions {
}
};
}
+
+ /**
+ * This class contains necessary information for Activity Scene Transition.
+ *
+ * @hide
+ */
+ public static class SceneTransitionInfo implements Parcelable {
+ private boolean mIsReturning;
+ private int mResultCode;
+ @Nullable
+ private Intent mResultData;
+ @Nullable
+ private ArrayList<String> mSharedElementNames;
+ @Nullable
+ private ResultReceiver mResultReceiver;
+ private int mExitCoordinatorIndex;
+
+ public SceneTransitionInfo() {
+ }
+
+ SceneTransitionInfo(Parcel in) {
+ mIsReturning = in.readBoolean();
+ mResultCode = in.readInt();
+ mResultData = in.readTypedObject(Intent.CREATOR);
+ mSharedElementNames = in.createStringArrayList();
+ mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
+ mExitCoordinatorIndex = in.readInt();
+ }
+
+ public static final Creator<SceneTransitionInfo> CREATOR = new Creator<>() {
+ @Override
+ public SceneTransitionInfo createFromParcel(Parcel in) {
+ return new SceneTransitionInfo(in);
+ }
+
+ @Override
+ public SceneTransitionInfo[] newArray(int size) {
+ return new SceneTransitionInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsReturning);
+ dest.writeInt(mResultCode);
+ dest.writeTypedObject(mResultData, flags);
+ dest.writeStringList(mSharedElementNames);
+ dest.writeTypedObject(mResultReceiver, flags);
+ dest.writeInt(mExitCoordinatorIndex);
+ }
+
+ public void setReturning(boolean isReturning) {
+ mIsReturning = isReturning;
+ }
+
+ public boolean isReturning() {
+ return mIsReturning;
+ }
+
+ public void setResultCode(int resultCode) {
+ mResultCode = resultCode;
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ public void setResultData(Intent resultData) {
+ mResultData = resultData;
+ }
+
+ @Nullable
+ public Intent getResultData() {
+ return mResultData;
+ }
+
+ public void setSharedElementNames(ArrayList<String> sharedElementNames) {
+ mSharedElementNames = sharedElementNames;
+ }
+
+ @Nullable
+ public ArrayList<String> getSharedElementNames() {
+ return mSharedElementNames;
+ }
+
+ public void setResultReceiver(ResultReceiver resultReceiver) {
+ mResultReceiver = resultReceiver;
+ }
+
+ @Nullable
+ public ResultReceiver getResultReceiver() {
+ return mResultReceiver;
+ }
+
+ public void setExitCoordinatorKey(int exitCoordinatorKey) {
+ mExitCoordinatorIndex = exitCoordinatorKey;
+ }
+
+ public int getExitCoordinatorKey() {
+ return mExitCoordinatorIndex;
+ }
+
+ boolean isCrossTask() {
+ return mExitCoordinatorIndex < 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SceneTransitionInfo, mIsReturning=" + mIsReturning
+ + ", mResultCode=" + mResultCode + ", mResultData=" + mResultData
+ + ", mSharedElementNames=" + mSharedElementNames
+ + ", mTransitionReceiver=" + mResultReceiver
+ + ", mExitCoordinatorIndex=" + mExitCoordinatorIndex;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7e5326e64398..949e2ba07a18 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@ import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
@@ -632,8 +633,8 @@ public final class ActivityThread extends ClientTransactionHandler
@UnsupportedAppUsage
boolean mPreserveWindow;
- /** The options for scene transition. */
- ActivityOptions mActivityOptions;
+ /** The scene transition info. */
+ SceneTransitionInfo mSceneTransitionInfo;
/** Whether this activiy was launched from a bubble. */
boolean mLaunchedFromBubble;
@@ -660,7 +661,7 @@ public final class ActivityThread extends ClientTransactionHandler
ActivityInfo info, Configuration overrideConfig,
String referrer, IVoiceInteractor voiceInteractor, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
+ List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
IBinder taskFragmentToken) {
@@ -680,7 +681,7 @@ public final class ActivityThread extends ClientTransactionHandler
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
- mActivityOptions = activityOptions;
+ mSceneTransitionInfo = sceneTransitionInfo;
mLaunchedFromBubble = launchedFromBubble;
mTaskFragmentToken = taskFragmentToken;
init();
@@ -1960,9 +1961,9 @@ public final class ActivityThread extends ClientTransactionHandler
sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
}
- public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
- sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
- new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
+ public void scheduleOnNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
+ sendMessage(H.ON_NEW_SCENE_TRANSITION_INFO,
+ new Pair<IBinder, SceneTransitionInfo>(token, info));
}
public void setProcessState(int state) {
@@ -2258,7 +2259,7 @@ public final class ActivityThread extends ClientTransactionHandler
public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
@UnsupportedAppUsage
public static final int INSTALL_PROVIDER = 145;
- public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
+ public static final int ON_NEW_SCENE_TRANSITION_INFO = 146;
@UnsupportedAppUsage
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
@@ -2314,7 +2315,7 @@ public final class ActivityThread extends ClientTransactionHandler
case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
- case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
+ case ON_NEW_SCENE_TRANSITION_INFO: return "ON_NEW_SCENE_TRANSITION_INFO";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
@@ -2520,9 +2521,10 @@ public final class ActivityThread extends ClientTransactionHandler
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
- case ON_NEW_ACTIVITY_OPTIONS:
- Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
- onNewActivityOptions(pair.first, pair.second);
+ case ON_NEW_SCENE_TRANSITION_INFO:
+ Pair<IBinder, SceneTransitionInfo> pair =
+ (Pair<IBinder, SceneTransitionInfo>) msg.obj;
+ onNewSceneTransitionInfo(pair.first, pair.second);
break;
case ENTER_ANIMATION_COMPLETE:
handleEnterAnimationComplete((IBinder) msg.obj);
@@ -3921,9 +3923,9 @@ public final class ActivityThread extends ClientTransactionHandler
activity.setTheme(theme);
}
- if (r.mActivityOptions != null) {
- activity.mPendingOptions = r.mActivityOptions;
- r.mActivityOptions = null;
+ if (r.mSceneTransitionInfo != null) {
+ activity.mSceneTransitionInfo = r.mSceneTransitionInfo;
+ r.mSceneTransitionInfo = null;
}
activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
activity.mCalled = false;
@@ -3962,7 +3964,7 @@ public final class ActivityThread extends ClientTransactionHandler
@Override
public void handleStartActivity(ActivityClientRecord r,
- PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
+ PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo) {
final Activity activity = r.activity;
if (!r.stopped) {
throw new IllegalStateException("Can't start activity that is not stopped.");
@@ -3973,8 +3975,8 @@ public final class ActivityThread extends ClientTransactionHandler
}
unscheduleGcIdler();
- if (activityOptions != null) {
- activity.mPendingOptions = activityOptions;
+ if (sceneTransitionInfo != null) {
+ activity.mSceneTransitionInfo = sceneTransitionInfo;
}
// Start
@@ -4349,10 +4351,10 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
- public void onNewActivityOptions(IBinder token, ActivityOptions options) {
+ public void onNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
- r.activity.onNewActivityOptions(options);
+ r.activity.onNewSceneTransitionInfo(info);
}
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 6f4bb456c478..d947a9b8eaa7 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -15,6 +15,7 @@
*/
package android.app;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.content.Intent;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -81,9 +82,9 @@ class ActivityTransitionState {
private EnterTransitionCoordinator mEnterTransitionCoordinator;
/**
- * ActivityOptions used on entering this Activity.
+ * {@link SceneTransitionInfo} used on entering this Activity.
*/
- private ActivityOptions mEnterActivityOptions;
+ private SceneTransitionInfo mEnterSceneTransitionInfo;
/**
* Has an exit transition been started? If so, we don't want to double-exit.
@@ -165,7 +166,7 @@ class ActivityTransitionState {
}
}
- public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+ public void setEnterSceneTransitionInfo(Activity activity, SceneTransitionInfo info) {
final Window window = activity.getWindow();
if (window == null) {
return;
@@ -173,16 +174,15 @@ class ActivityTransitionState {
// ensure Decor View has been created so that the window features are activated
window.getDecorView();
if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
- && options != null && mEnterActivityOptions == null
- && mEnterTransitionCoordinator == null
- && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- mEnterActivityOptions = options;
+ && info != null && mEnterSceneTransitionInfo == null
+ && mEnterTransitionCoordinator == null) {
+ mEnterSceneTransitionInfo = info;
mIsEnterTriggered = false;
- if (mEnterActivityOptions.isReturning()) {
+ if (mEnterSceneTransitionInfo.isReturning()) {
restoreExitedViews();
- int result = mEnterActivityOptions.getResultCode();
+ int result = mEnterSceneTransitionInfo.getResultCode();
if (result != 0) {
- Intent intent = mEnterActivityOptions.getResultData();
+ Intent intent = mEnterSceneTransitionInfo.getResultData();
if (intent != null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
@@ -193,25 +193,26 @@ class ActivityTransitionState {
}
public void enterReady(Activity activity) {
- if (mEnterActivityOptions == null || mIsEnterTriggered) {
+ if (mEnterSceneTransitionInfo == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
- ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
- ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
- final boolean isReturning = mEnterActivityOptions.isReturning();
+ final ArrayList<String> sharedElementNames =
+ mEnterSceneTransitionInfo.getSharedElementNames();
+ ResultReceiver resultReceiver = mEnterSceneTransitionInfo.getResultReceiver();
+ final boolean isReturning = mEnterSceneTransitionInfo.isReturning();
if (isReturning) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
getPendingExitNames(); // Set mPendingExitNames before resetting mEnterTransitionCoordinator
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
- resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
- mEnterActivityOptions.isCrossTask());
- if (mEnterActivityOptions.isCrossTask()) {
- mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
- mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+ resultReceiver, sharedElementNames, mEnterSceneTransitionInfo.isReturning(),
+ mEnterSceneTransitionInfo.isCrossTask());
+ if (mEnterSceneTransitionInfo.isCrossTask() && sharedElementNames != null) {
+ mExitingFrom = new ArrayList<>(sharedElementNames);
+ mExitingTo = new ArrayList<>(sharedElementNames);
}
if (!mIsEnterPostponed) {
@@ -248,7 +249,7 @@ class ActivityTransitionState {
mExitingFrom = null;
mExitingTo = null;
mExitingToView = null;
- mEnterActivityOptions = null;
+ mEnterSceneTransitionInfo = null;
}
public void onStop(Activity activity) {
@@ -296,7 +297,7 @@ class ActivityTransitionState {
mExitingToView = null;
mCalledExitCoordinator = null;
mEnterTransitionCoordinator = null;
- mEnterActivityOptions = null;
+ mEnterSceneTransitionInfo = null;
mExitTransitionCoordinators = null;
}
@@ -386,9 +387,10 @@ class ActivityTransitionState {
mExitTransitionCoordinators == null) {
return;
}
- ActivityOptions activityOptions = new ActivityOptions(options);
- if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- int key = activityOptions.getExitCoordinatorKey();
+ final ActivityOptions activityOptions = new ActivityOptions(options);
+ final SceneTransitionInfo info = activityOptions.getSceneTransitionInfo();
+ if (info != null) {
+ int key = info.getExitCoordinatorKey();
int index = mExitTransitionCoordinators.indexOfKey(key);
if (index >= 0) {
mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 287d2bd9e6a7..34c44f9489d5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -131,6 +131,7 @@ import dalvik.system.VMRuntime;
import libcore.util.EmptyArray;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -4038,11 +4039,11 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ public <T> T parseAndroidManifest(@NonNull File apkFile,
@NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
- Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+ Objects.requireNonNull(apkFile, "apkFile cannot be null");
Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
- try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+ try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFile)) {
return parserFunction.apply(xmlResourceParser);
} catch (IOException e) {
Log.w(TAG, "Failed to get the android manifest parser", e);
@@ -4050,11 +4051,11 @@ public class ApplicationPackageManager extends PackageManager {
}
}
- private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+ private static XmlResourceParser getAndroidManifestParser(@NonNull File apkFile)
throws IOException {
ApkAssets apkAssets = null;
try {
- apkAssets = ApkAssets.loadFromPath(apkFilePath);
+ apkAssets = ApkAssets.loadFromPath(apkFile.getAbsolutePath());
return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
} finally {
if (apkAssets != null) {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f9ab55e00dc6..5b354fc3b9ed 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,6 +23,7 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.NotificationManager.InterruptionFilter;
import android.content.ComponentName;
import android.net.Uri;
@@ -35,6 +36,7 @@ import android.view.WindowInsetsController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Objects;
/**
@@ -111,6 +113,30 @@ public final class AutomaticZenRule implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
+ /** Used to track which rule variables have been modified by the user.
+ * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FIELD_" }, value = {
+ FIELD_NAME,
+ FIELD_INTERRUPTION_FILTER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModifiableField {}
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int FIELD_NAME = 1 << 0;
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
+
private boolean enabled;
private String name;
private @InterruptionFilter int interruptionFilter;
@@ -120,12 +146,14 @@ public final class AutomaticZenRule implements Parcelable {
private long creationTime;
private ZenPolicy mZenPolicy;
private ZenDeviceEffects mDeviceEffects;
+ // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
private boolean mModified = false;
private String mPkg;
- private int mType = TYPE_UNKNOWN;
+ private int mType = Flags.modesApi() ? TYPE_UNKNOWN : 0;
private int mIconResId;
private String mTriggerDescription;
private boolean mAllowManualInvocation;
+ private @ModifiableField int mUserModifiedFields; // Bitwise representation
/**
* The maximum string length for any string contained in this automatic zen rule. This pertains
@@ -228,6 +256,7 @@ public final class AutomaticZenRule implements Parcelable {
mIconResId = source.readInt();
mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
mType = source.readInt();
+ mUserModifiedFields = source.readInt();
}
}
@@ -278,6 +307,8 @@ public final class AutomaticZenRule implements Parcelable {
* Returns whether this rule's name has been modified by the user.
* @hide
*/
+ // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once
+ // FLAG_MODES_API is inlined.
public boolean isModified() {
return mModified;
}
@@ -475,6 +506,32 @@ public final class AutomaticZenRule implements Parcelable {
return type;
}
+ /**
+ * Gets the bitmask representing which fields are user modified. Bits are set using
+ * {@link ModifiableField}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public @ModifiableField int getUserModifiedFields() {
+ return mUserModifiedFields;
+ }
+
+ /**
+ * Returns {@code true} if the {@link AutomaticZenRule} can be updated.
+ * When this returns {@code false}, calls to
+ * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule
+ * will ignore changes to user-configurable fields.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean canUpdate() {
+ // The rule is considered updateable if its bitmask has no user modifications, and
+ // the bitmasks of the policy and device effects have no modification.
+ return mUserModifiedFields == 0
+ && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0)
+ && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -503,6 +560,7 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeInt(mIconResId);
dest.writeString(mTriggerDescription);
dest.writeInt(mType);
+ dest.writeInt(mUserModifiedFields);
}
}
@@ -524,12 +582,26 @@ public final class AutomaticZenRule implements Parcelable {
.append(",allowManualInvocation=").append(mAllowManualInvocation)
.append(",iconResId=").append(mIconResId)
.append(",triggerDescription=").append(mTriggerDescription)
- .append(",type=").append(mType);
+ .append(",type=").append(mType)
+ .append(",userModifiedFields=")
+ .append(modifiedFieldsToString(mUserModifiedFields));
}
return sb.append(']').toString();
}
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ private String modifiedFieldsToString(int bitmask) {
+ ArrayList<String> modified = new ArrayList<>();
+ if ((bitmask & FIELD_NAME) != 0) {
+ modified.add("FIELD_NAME");
+ }
+ if ((bitmask & FIELD_INTERRUPTION_FILTER) != 0) {
+ modified.add("FIELD_INTERRUPTION_FILTER");
+ }
+ return "{" + String.join(",", modified) + "}";
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof AutomaticZenRule)) return false;
@@ -551,7 +623,8 @@ public final class AutomaticZenRule implements Parcelable {
&& other.mAllowManualInvocation == mAllowManualInvocation
&& other.mIconResId == mIconResId
&& Objects.equals(other.mTriggerDescription, mTriggerDescription)
- && other.mType == mType;
+ && other.mType == mType
+ && other.mUserModifiedFields == mUserModifiedFields;
}
return finalEquals;
}
@@ -561,7 +634,8 @@ public final class AutomaticZenRule implements Parcelable {
if (Flags.modesApi()) {
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
- mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
+ mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType,
+ mUserModifiedFields);
}
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -630,6 +704,7 @@ public final class AutomaticZenRule implements Parcelable {
private boolean mAllowManualInvocation;
private long mCreationTime;
private String mPkg;
+ private @ModifiableField int mUserModifiedFields;
public Builder(@NonNull AutomaticZenRule rule) {
mName = rule.getName();
@@ -646,6 +721,7 @@ public final class AutomaticZenRule implements Parcelable {
mAllowManualInvocation = rule.isManualInvocationAllowed();
mCreationTime = rule.getCreationTime();
mPkg = rule.getPackageName();
+ mUserModifiedFields = rule.mUserModifiedFields;
}
public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -772,6 +848,19 @@ public final class AutomaticZenRule implements Parcelable {
return this;
}
+ /**
+ * Sets the bitmask representing which fields have been user-modified.
+ * This method should not be used outside of tests. The value of userModifiedFields
+ * should be set based on what values are changed when a rule is populated or updated..
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
+ mUserModifiedFields = userModifiedFields;
+ return this;
+ }
+
public @NonNull AutomaticZenRule build() {
AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
mConditionId, mPolicy, mInterruptionFilter, mEnabled);
@@ -782,6 +871,7 @@ public final class AutomaticZenRule implements Parcelable {
rule.mIconResId = mIconResId;
rule.mAllowManualInvocation = mAllowManualInvocation;
rule.setPackageName(mPkg);
+ rule.mUserModifiedFields = mUserModifiedFields;
return rule;
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 25075e91088f..b30067491a84 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -17,6 +17,7 @@ package android.app;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
@@ -207,7 +208,7 @@ public abstract class ClientTransactionHandler {
/** Perform activity start. */
public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
- PendingTransactionActions pendingActions, ActivityOptions activityOptions);
+ PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo);
/** Get package info. */
public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index ac9c497f2a36..d1e517bbd03c 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -30,6 +30,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
@@ -577,6 +578,26 @@ public abstract class ForegroundServiceTypePolicy {
);
/**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROCESSING =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(
+ Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING)
+ }, true),
+ null /* anyOfPermissions */,
+ null /* permissionEnforcementFlag */,
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
+ );
+
+ /**
* The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
*
* @hide
@@ -1331,6 +1352,8 @@ public abstract class ForegroundServiceTypePolicy {
FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FGS_TYPE_POLICY_SHORT_SERVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+ FGS_TYPE_POLICY_MEDIA_PROCESSING);
// TODO (b/271950506): revisit it in the next release.
// Hide the file management type for now. If anyone uses it, will default to "none".
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a301c18bd374..59e0e9962fac 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -16,6 +16,7 @@
package android.app;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ContentProviderHolder;
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
@@ -114,7 +115,7 @@ oneway interface IApplicationThread {
void scheduleCreateBackupAgent(in ApplicationInfo app,
int backupMode, int userId, int operationType);
void scheduleDestroyBackupAgent(in ApplicationInfo app, int userId);
- void scheduleOnNewActivityOptions(IBinder token, in Bundle options);
+ void scheduleOnNewSceneTransitionInfo(IBinder token, in SceneTransitionInfo info);
void scheduleSuicide();
void dispatchPackageBroadcast(int cmd, in String[] packages);
void scheduleCrash(in String msg, int typeId, in Bundle extras);
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 6b5f19a52e33..1b19ecdd5931 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -179,7 +179,7 @@ public class LocalActivityManager {
}
mActivityThread.handleStartActivity(clientRecord, pendingActions,
- null /* activityOptions */);
+ null /* sceneTransitionInfo */);
r.curState = STARTED;
if (desiredState == RESUMED) {
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 1190bf6a604b..4d53701772e4 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -23,7 +23,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
@@ -73,7 +73,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
private PersistableBundle mPersistentState;
private List<ResultInfo> mPendingResults;
private List<ReferrerIntent> mPendingNewIntents;
- private ActivityOptions mActivityOptions;
+ private SceneTransitionInfo mSceneTransitionInfo;
private boolean mIsForward;
private ProfilerInfo mProfilerInfo;
private IBinder mAssistToken;
@@ -104,8 +104,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo,
mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
- mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+ mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward,
+ mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
mTaskFragmentToken);
client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -136,7 +136,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
@Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
@Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable ActivityOptions activityOptions,
+ @Nullable SceneTransitionInfo sceneTransitionInfo,
boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -152,7 +152,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
persistentState != null ? new PersistableBundle(persistentState) : null,
pendingResults != null ? new ArrayList<>(pendingResults) : null,
pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
- activityOptions, isForward,
+ sceneTransitionInfo, isForward,
profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
assistToken, activityClientController, shareableActivityToken,
launchedFromBubble, taskFragmentToken);
@@ -193,7 +193,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
dest.writePersistableBundle(mPersistentState);
dest.writeTypedList(mPendingResults, flags);
dest.writeTypedList(mPendingNewIntents, flags);
- dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+ dest.writeTypedObject(mSceneTransitionInfo, flags);
dest.writeBoolean(mIsForward);
dest.writeTypedObject(mProfilerInfo, flags);
dest.writeStrongBinder(mAssistToken);
@@ -213,7 +213,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
in.readPersistableBundle(getClass().getClassLoader()),
in.createTypedArrayList(ResultInfo.CREATOR),
in.createTypedArrayList(ReferrerIntent.CREATOR),
- ActivityOptions.fromBundle(in.readBundle()), in.readBoolean(),
+ in.readTypedObject(SceneTransitionInfo.CREATOR),
+ in.readBoolean(),
in.readTypedObject(ProfilerInfo.CREATOR),
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
@@ -253,7 +254,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
&& areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
&& Objects.equals(mPendingResults, other.mPendingResults)
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
- && (mActivityOptions == null) == (other.mActivityOptions == null)
+ && (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null)
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
@@ -276,7 +277,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
result = 31 * result + getRoughBundleHashCode(mPersistentState);
result = 31 * result + Objects.hashCode(mPendingResults);
result = 31 * result + Objects.hashCode(mPendingNewIntents);
- result = 31 * result + (mActivityOptions != null ? 1 : 0);
+ result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
result = 31 * result + (mIsForward ? 1 : 0);
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
@@ -325,7 +326,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
+ ",persistentState=" + mPersistentState
+ ",pendingResults=" + mPendingResults
+ ",pendingNewIntents=" + mPendingNewIntents
- + ",options=" + mActivityOptions
+ + ",sceneTransitionInfo=" + mSceneTransitionInfo
+ ",profilerInfo=" + mProfilerInfo
+ ",assistToken=" + mAssistToken
+ ",shareableActivityToken=" + mShareableActivityToken + "}";
@@ -340,7 +341,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
@Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingNewIntents,
- @Nullable ActivityOptions activityOptions, boolean isForward,
+ @Nullable SceneTransitionInfo sceneTransitionInfo, boolean isForward,
@Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -359,7 +360,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
instance.mPersistentState = persistentState;
instance.mPendingResults = pendingResults;
instance.mPendingNewIntents = pendingNewIntents;
- instance.mActivityOptions = activityOptions;
+ instance.mSceneTransitionInfo = sceneTransitionInfo;
instance.mIsForward = isForward;
instance.mProfilerInfo = profilerInfo;
instance.mAssistToken = assistToken;
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 8b98b2184043..a0f93ce00ec8 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -20,7 +20,7 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
@@ -35,13 +35,13 @@ public class StartActivityItem extends ActivityLifecycleItem {
private static final String TAG = "StartActivityItem";
- private ActivityOptions mActivityOptions;
+ private SceneTransitionInfo mSceneTransitionInfo;
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
- client.handleStartActivity(r, pendingActions, mActivityOptions);
+ client.handleStartActivity(r, pendingActions, mSceneTransitionInfo);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -57,13 +57,13 @@ public class StartActivityItem extends ActivityLifecycleItem {
/** Obtain an instance initialized with provided params. */
@NonNull
public static StartActivityItem obtain(@NonNull IBinder activityToken,
- @Nullable ActivityOptions activityOptions) {
+ @Nullable SceneTransitionInfo sceneTransitionInfo) {
StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
if (instance == null) {
instance = new StartActivityItem();
}
instance.setActivityToken(activityToken);
- instance.mActivityOptions = activityOptions;
+ instance.mSceneTransitionInfo = sceneTransitionInfo;
return instance;
}
@@ -71,7 +71,7 @@ public class StartActivityItem extends ActivityLifecycleItem {
@Override
public void recycle() {
super.recycle();
- mActivityOptions = null;
+ mSceneTransitionInfo = null;
ObjectPool.recycle(this);
}
@@ -81,13 +81,13 @@ public class StartActivityItem extends ActivityLifecycleItem {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+ dest.writeTypedObject(mSceneTransitionInfo, flags);
}
/** Read from Parcel. */
private StartActivityItem(@NonNull Parcel in) {
super(in);
- mActivityOptions = ActivityOptions.fromBundle(in.readBundle());
+ mSceneTransitionInfo = in.readTypedObject(SceneTransitionInfo.CREATOR);
}
public static final @NonNull Creator<StartActivityItem> CREATOR = new Creator<>() {
@@ -109,21 +109,21 @@ public class StartActivityItem extends ActivityLifecycleItem {
return false;
}
final StartActivityItem other = (StartActivityItem) o;
- return (mActivityOptions == null) == (other.mActivityOptions == null);
+ return (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + super.hashCode();
- result = 31 * result + (mActivityOptions != null ? 1 : 0);
+ result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
return result;
}
@Override
public String toString() {
return "StartActivityItem{" + super.toString()
- + ",options=" + mActivityOptions + "}";
+ + ",sceneTransitionInfo=" + mSceneTransitionInfo + "}";
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 2e47feeef068..ba940770898a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -324,7 +324,7 @@ public class TransactionExecutor {
break;
case ON_START:
mTransactionHandler.handleStartActivity(r, mPendingActions,
- null /* activityOptions */);
+ null /* sceneTransitionInfo */);
break;
case ON_RESUME:
mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index cdb92acc5256..843158c0e9fb 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -71,6 +71,12 @@ public final class AssociationInfo implements Parcelable {
* @see CompanionDeviceManager#disassociate(int)
*/
private final boolean mRevoked;
+ /**
+ * Indicates that the association is waiting for its corresponding companion app to be installed
+ * before it can be added to CDM. This is likely because it was restored onto the device from a
+ * backup.
+ */
+ private final boolean mPending;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
@@ -88,7 +94,7 @@ public final class AssociationInfo implements Parcelable {
@Nullable String tag, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String deviceProfile,
@Nullable AssociatedDevice associatedDevice, boolean selfManaged,
- boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs,
+ boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
long lastTimeConnectedMs, int systemDataSyncFlags) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
@@ -109,6 +115,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
mRevoked = revoked;
+ mPending = pending;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
mSystemDataSyncFlags = systemDataSyncFlags;
@@ -236,6 +243,15 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * @return true if the association is waiting for its corresponding app to be installed
+ * before it can be added to CDM.
+ * @hide
+ */
+ public boolean isPending() {
+ return mPending;
+ }
+
+ /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
@@ -318,6 +334,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mAssociatedDevice=" + mAssociatedDevice
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ ", mRevoked=" + mRevoked
+ + ", mPending=" + mPending
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -336,6 +353,7 @@ public final class AssociationInfo implements Parcelable {
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
&& mRevoked == that.mRevoked
+ && mPending == that.mPending
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -351,7 +369,7 @@ public final class AssociationInfo implements Parcelable {
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
- mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
+ mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
}
@Override
@@ -372,6 +390,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
dest.writeBoolean(mRevoked);
+ dest.writeBoolean(mPending);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
dest.writeInt(mSystemDataSyncFlags);
@@ -389,6 +408,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
mRevoked = in.readBoolean();
+ mPending = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
mSystemDataSyncFlags = in.readInt();
@@ -427,6 +447,7 @@ public final class AssociationInfo implements Parcelable {
private boolean mSelfManaged;
private boolean mNotifyOnDeviceNearby;
private boolean mRevoked;
+ private boolean mPending;
private long mTimeApprovedMs;
private long mLastTimeConnectedMs;
private int mSystemDataSyncFlags;
@@ -453,6 +474,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = info.mSelfManaged;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
mRevoked = info.mRevoked;
+ mPending = info.mPending;
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
@@ -476,6 +498,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = info.mSelfManaged;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
mRevoked = info.mRevoked;
+ mPending = info.mPending;
mTimeApprovedMs = info.mTimeApprovedMs;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
mSystemDataSyncFlags = info.mSystemDataSyncFlags;
@@ -549,6 +572,14 @@ public final class AssociationInfo implements Parcelable {
}
/** @hide */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setPending(boolean pending) {
+ mPending = pending;
+ return this;
+ }
+
+ /** @hide */
@TestApi
@NonNull
@SuppressLint("MissingGetterMatchingBuilder")
@@ -606,6 +637,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged,
mNotifyOnDeviceNearby,
mRevoked,
+ mPending,
mTimeApprovedMs,
mLastTimeConnectedMs,
mSystemDataSyncFlags
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ee1d117bf71c..d5eee63fee12 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8099,7 +8099,7 @@ public class Intent implements Parcelable, Cloneable {
int end = data.indexOf('/', 14);
if (end < 0) {
// All we have is a package name.
- intent.mPackage = data.substring(14);
+ intent.mPackage = Uri.decodeIfNeeded(data.substring(14));
if (!explicitAction) {
intent.setAction(ACTION_MAIN);
}
@@ -8107,21 +8107,22 @@ public class Intent implements Parcelable, Cloneable {
} else {
// Target the Intent at the given package name always.
String authority = null;
- intent.mPackage = data.substring(14, end);
+ intent.mPackage = Uri.decodeIfNeeded(data.substring(14, end));
int newEnd;
if ((end+1) < data.length()) {
if ((newEnd=data.indexOf('/', end+1)) >= 0) {
// Found a scheme, remember it.
- scheme = data.substring(end+1, newEnd);
+ scheme = Uri.decodeIfNeeded(data.substring(end + 1, newEnd));
end = newEnd;
if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
// Found a authority, remember it.
- authority = data.substring(end+1, newEnd);
+ authority = Uri.decodeIfNeeded(
+ data.substring(end + 1, newEnd));
end = newEnd;
}
} else {
// All we have is a scheme.
- scheme = data.substring(end+1);
+ scheme = Uri.decodeIfNeeded(data.substring(end + 1));
}
}
if (scheme == null) {
@@ -11762,27 +11763,33 @@ public class Intent implements Parcelable, Cloneable {
+ this);
}
uri.append("android-app://");
- uri.append(mPackage);
+ uri.append(Uri.encode(mPackage));
String scheme = null;
if (mData != null) {
- scheme = mData.getScheme();
+ // All values here must be wrapped with Uri#encodeIfNotEncoded because it is
+ // possible to exploit the Uri API to return a raw unencoded value, which will
+ // not deserialize properly and may cause the resulting Intent to be transformed
+ // to a malicious value.
+ scheme = Uri.encodeIfNotEncoded(mData.getScheme(), null);
if (scheme != null) {
uri.append('/');
uri.append(scheme);
- String authority = mData.getEncodedAuthority();
+ String authority = Uri.encodeIfNotEncoded(mData.getEncodedAuthority(), null);
if (authority != null) {
uri.append('/');
uri.append(authority);
- String path = mData.getEncodedPath();
+
+ // Multiple path segments are allowed, don't encode the path / separator
+ String path = Uri.encodeIfNotEncoded(mData.getEncodedPath(), "/");
if (path != null) {
uri.append(path);
}
- String queryParams = mData.getEncodedQuery();
+ String queryParams = Uri.encodeIfNotEncoded(mData.getEncodedQuery(), null);
if (queryParams != null) {
uri.append('?');
uri.append(queryParams);
}
- String fragment = mData.getEncodedFragment();
+ String fragment = Uri.encodeIfNotEncoded(mData.getEncodedFragment(), null);
if (fragment != null) {
uri.append('#');
uri.append(fragment);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 30871e938e68..9fe8af516694 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -23,7 +23,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
-import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
@@ -37,7 +36,6 @@ import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Printer;
import android.window.OnBackInvokedCallback;
@@ -1790,8 +1788,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* @hide
*/
public boolean isChangeEnabled(long changeId) {
- return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
- UserHandle.getUserHandleForUid(applicationInfo.uid));
+ return applicationInfo.isChangeEnabled(changeId);
}
/** @hide */
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 869c621e8564..f0a89960cad1 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -2645,6 +2646,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
/**
+ * Checks if a changeId is enabled for the current user
+ * @param changeId The changeId to verify
+ * @return True of the changeId is enabled
+ * @hide
+ */
+ public boolean isChangeEnabled(long changeId) {
+ return CompatChanges.isChangeEnabled(changeId, packageName,
+ UserHandle.getUserHandleForUid(uid));
+ }
+
+ /**
* @return whether the app has requested exemption from the foreground service restrictions.
* This does not take any affect for now.
* @hide
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
index a1c874725d4b..c6e93bb302bb 100644
--- a/core/java/android/content/pm/ModuleInfo.java
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -16,7 +16,6 @@
package android.content.pm;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -122,18 +121,15 @@ public final class ModuleInfo implements Parcelable {
return mApexModuleName;
}
- /** @hide Sets the list of the package name of APK-in-APEX apps in this module. */
+ /** @hide Set the list of the package names of all APK-in-APEX apps in this module. */
public ModuleInfo setApkInApexPackageNames(@NonNull Collection<String> apkInApexPackageNames) {
Objects.requireNonNull(apkInApexPackageNames);
mApkInApexPackageNames = List.copyOf(apkInApexPackageNames);
return this;
}
- /**
- * Gets the list of the package name of all APK-in-APEX apps in the module.
- */
+ /** @hide Get the list of the package names of all APK-in-APEX apps in the module. */
@NonNull
- @FlaggedApi(android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
public Collection<String> getApkInApexPackageNames() {
if (mApkInApexPackageNames == null) {
return Collections.emptyList();
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 0e131b413d0c..433226413917 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -672,6 +672,13 @@ public class PackageInstaller {
public @interface UserActionReason {}
/**
+ * The unarchival status is not set.
+ *
+ * @hide
+ */
+ public static final int UNARCHIVAL_STATUS_UNSET = -1;
+
+ /**
* The unarchival is possible and will commence.
*
* <p> Note that this does not mean that the unarchival has completed. This status should be
@@ -736,6 +743,7 @@ public class PackageInstaller {
* @hide
*/
@IntDef(value = {
+ UNARCHIVAL_STATUS_UNSET,
UNARCHIVAL_OK,
UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
@@ -2696,8 +2704,6 @@ public class PackageInstaller {
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
- /** {@hide} */
- public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2750,7 +2756,6 @@ public class PackageInstaller {
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
- unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2785,7 +2790,6 @@ public class PackageInstaller {
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
- ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3495,7 +3499,6 @@ public class PackageInstaller {
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
- pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3540,7 +3543,6 @@ public class PackageInstaller {
dest.writeBoolean(applicationEnabledSettingPersistent);
dest.writeInt(developmentInstallFlags);
dest.writeInt(unarchiveId);
- dest.writeParcelable(unarchiveIntentSender, flags);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0fb0993d0f04..8e5e8250c85d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11534,14 +11534,14 @@ public abstract class PackageManager {
}
/**
- * Retrieve AndroidManifest.xml information for the given application apk path.
+ * Retrieve AndroidManifest.xml information for the given application apk file.
*
* <p>Example:
*
* <pre><code>
* Bundle result;
* try {
- * result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+ * result = getContext().getPackageManager().parseAndroidManifest(apkFile,
* xmlResourceParser -> {
* Bundle bundle = new Bundle();
* // Search the start tag
@@ -11570,9 +11570,10 @@ public abstract class PackageManager {
*
* Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
* information by the XmlResourceParser object. After leaving the parserFunction, the
- * XmlResourceParser object will be closed.
+ * XmlResourceParser object will be closed. The caller should also handle the exception for
+ * calling this method.
*
- * @param apkFilePath The path of an application apk file.
+ * @param apkFile The file of an application apk.
* @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
* after getting the AndroidManifest.xml of an application package.
*
@@ -11583,7 +11584,7 @@ public abstract class PackageManager {
*/
@FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
@WorkerThread
- public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ public <T> T parseAndroidManifest(@NonNull File apkFile,
@NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
throw new UnsupportedOperationException(
"parseAndroidManifest not implemented in subclass");
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 012b6c49f4c5..cdda12eebdc4 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,9 +273,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
* to the <code>retailDemo</code> value of
* {@link android.R.attr#protectionLevel}.
*
- * @deprecated This flag has been replaced by the retail demo role and is a no-op since Android
- * V.
- *
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4d704c34195f..ae46c027505e 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
import android.os.Parcel;
@@ -471,6 +472,17 @@ public class ServiceInfo extends ComponentInfo
public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
/**
+ * Constant corresponding to {@code mediaProcessing} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * Media processing use cases such as video or photo editing and processing.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
+ )
+ @FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE)
+ public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 1 << 13;
+
+ /**
* Constant corresponding to {@code specialUse} in
* the {@link android.R.attr#foregroundServiceType} attribute.
* Use cases that can't be categorized into any other foreground service types, but also
@@ -554,6 +566,7 @@ public class ServiceInfo extends ComponentInfo
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -640,6 +653,8 @@ public class ServiceInfo extends ComponentInfo
return "shortService";
case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
return "fileManagement";
+ case FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:
+ return "mediaProcessing";
case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
return "specialUse";
default:
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 94bec3562bb0..a2cd3e153b3e 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -131,3 +131,18 @@ flag {
bug: "310801107"
is_fixed_read_only: true
}
+
+flag {
+ name: "introduce_media_processing_type"
+ namespace: "backstage_power"
+ description: "Add a new FGS type for media processing use cases."
+ bug: "317788011"
+}
+
+flag {
+ name: "encode_app_intent"
+ namespace: "package_manager_service"
+ description: "Feature flag to encode app intent."
+ bug: "281848623"
+}
+
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index e511469262d1..89f4985461b7 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -26,6 +26,8 @@ import androidx.annotation.StyleableRes;
import com.android.internal.R;
+import java.util.Set;
+
/**
* Defines the string attribute length and child tag count restrictions for a xml element.
*
@@ -37,7 +39,11 @@ public class Element {
private static final int MAX_ATTR_LEN_URL_COMPONENT = 256;
private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
private static final int MAX_ATTR_LEN_PACKAGE = 256;
- private static final int MAX_ATTR_LEN_MIMETYPE = 512;
+ /**
+ * The mime type max length restriction here should match the restriction that is also
+ * placed in {@link android.content.pm.PackageManager#setMimeGroup(String, Set)}
+ */
+ private static final int MAX_ATTR_LEN_MIMETYPE = 255;
private static final int MAX_ATTR_LEN_NAME = 1024;
private static final int MAX_ATTR_LEN_PATH = 4000;
private static final int MAX_ATTR_LEN_VALUE = 32_768;
@@ -103,6 +109,7 @@ public class Element {
protected static final String TAG_ATTR_HOST = "host";
protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
protected static final String TAG_ATTR_MIMETYPE = "mimeType";
+ protected static final String TAG_ATTR_MIMEGROUP = "mimeGroup";
protected static final String TAG_ATTR_NAME = "name";
protected static final String TAG_ATTR_PACKAGE = "package";
protected static final String TAG_ATTR_PATH = "path";
@@ -367,6 +374,7 @@ public class Element {
case TAG_ATTR_BACKUP_AGENT:
case TAG_ATTR_CATEGORY:
case TAG_ATTR_MANAGE_SPACE_ACTIVITY:
+ case TAG_ATTR_MIMEGROUP:
case TAG_ATTR_NAME:
case TAG_ATTR_PARENT_ACTIVITY_NAME:
case TAG_ATTR_PERMISSION:
@@ -520,6 +528,8 @@ public class Element {
return MAX_ATTR_LEN_URL_COMPONENT;
case R.styleable.AndroidManifestData_mimeType:
return MAX_ATTR_LEN_MIMETYPE;
+ case R.styleable.AndroidManifestData_mimeGroup:
+ return MAX_ATTR_LEN_NAME;
case R.styleable.AndroidManifestData_path:
case R.styleable.AndroidManifestData_pathPattern:
case R.styleable.AndroidManifestData_pathPrefix:
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 3fcb3daaa1f2..47ee76e50c9a 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -755,7 +756,10 @@ public final class CredentialManager {
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index b96d83247591..ecffe9e5a8b2 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -121,8 +121,12 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
// The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
private long mConnectionPtr;
+ // Restrict this connection to read-only operations.
private boolean mOnlyAllowReadOnlyOperations;
+ // Allow this connection to treat updates to temporary tables as read-only operations.
+ private boolean mAllowTempTableRetry = Flags.sqliteAllowTempTables();
+
// The number of times attachCancellationSignal has been called.
// Because SQLite statement execution can be reentrant, we keep track of how many
// times we have attempted to attach a cancellation signal to the connection so that
@@ -142,6 +146,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
+ private static native boolean nativeUpdatesTempOnly(long connectionPtr, long statementPtr);
private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
int index);
@@ -1097,7 +1102,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
try {
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
final int type = DatabaseUtils.getSqlStatementTypeExtended(sql);
- final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
+ boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly,
seqNum);
if (!skipCache && isCacheable(type)) {
@@ -1265,13 +1270,20 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
/**
* Verify that the statement is read-only, if the connection only allows read-only
- * operations.
+ * operations. If the connection allows updates to temporary tables, then the statement is
+ * read-only if the only updates are to temporary tables.
* @param statement The statement to check.
* @throws SQLiteException if the statement could update the database inside a read-only
* transaction.
*/
void throwIfStatementForbidden(PreparedStatement statement) {
if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
+ if (mAllowTempTableRetry) {
+ statement.mReadOnly =
+ nativeUpdatesTempOnly(mConnectionPtr, statement.mStatementPtr);
+ if (statement.mReadOnly) return;
+ }
+
throw new SQLiteException("Cannot execute this statement because it "
+ "might modify the database but the connection is read-only.");
}
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 62a51236a2e2..92ef9c24c4ef 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -7,3 +7,11 @@ flag {
description: "SQLite APIs held back for Android 15"
bug: "279043253"
}
+
+flag {
+ name: "sqlite_allow_temp_tables"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Permit updates to TEMP tables in read-only transactions"
+ bug: "317993835"
+}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 9e09759a4282..56f69a67c0b5 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -450,11 +450,14 @@ public final class VirtualDisplayConfig implements Parcelable {
* automatically launched upon the display creation. If unset or set to {@code false}, the
* display will not host any activities upon creation.</p>
*
- * <p>Note: setting to {@code true} requires the display to be trusted. If the display is
- * not trusted, this property is ignored.</p>
+ * <p>Note: setting to {@code true} requires the display to be trusted and to not mirror
+ * content of other displays. If the display is not trusted, or if it mirrors content of
+ * other displays, this property is ignored.</p>
*
* @param isHomeSupported whether home activities are supported on the display
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
* @hide
*/
@FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_HOME)
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 70de477e9e2e..05a3e182135c 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
+import android.content.pm.Flags;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1971,6 +1972,42 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
/**
+ * Encodes a value it wasn't already encoded.
+ *
+ * @param value string to encode
+ * @param allow characters to allow
+ * @return encoded value
+ * @hide
+ */
+ public static String encodeIfNotEncoded(@Nullable String value, @Nullable String allow) {
+ if (value == null) return null;
+ if (!Flags.encodeAppIntent() || isEncoded(value, allow)) return value;
+ return encode(value, allow);
+ }
+
+ /**
+ * Returns true if the given string is already encoded to safe characters.
+ *
+ * @param value string to check
+ * @param allow characters to allow
+ * @return true if the string is already encoded or false if it should be encoded
+ */
+ private static boolean isEncoded(@Nullable String value, @Nullable String allow) {
+ if (value == null) return true;
+ for (int index = 0; index < value.length(); index++) {
+ char c = value.charAt(index);
+
+ // Allow % because that's the prefix for an encoded character. This method will fail
+ // for decoded strings whose onlyinvalid character is %, but it's assumed that % alone
+ // cannot cause malicious behavior in the framework.
+ if (!isAllowed(c, allow) && c != '%') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Decodes '%'-escaped octets in the given string using the UTF-8 scheme.
* Replaces invalid octets with the unicode replacement character
* ("\\uFFFD").
@@ -1988,6 +2025,18 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
/**
+ * Decodes a string if it was encoded, indicated by containing a %.
+ * @param value encoded string to decode
+ * @return decoded value
+ * @hide
+ */
+ public static String decodeIfNeeded(@Nullable String value) {
+ if (value == null) return null;
+ if (Flags.encodeAppIntent() && value.contains("%")) return decode(value);
+ return value;
+ }
+
+ /**
* Support for part implementations.
*/
static abstract class AbstractPart {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a9b7257a5406..58717179d64d 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1315,9 +1315,7 @@ public class Build {
if (IS_ENG) return true;
if (IS_TREBLE_ENABLED) {
- // If we can run this code, the device should already pass AVB.
- // So, we don't need to check AVB here.
- int result = VintfObject.verifyWithoutAvb();
+ int result = VintfObject.verifyBuildAtBoot();
if (result != 0) {
Slog.e(TAG, "Vendor interface is incompatible, error="
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 61b24aa55e30..b7649ba9700b 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -52,4 +52,9 @@ interface ISystemConfig {
* @see SystemConfigManager#getDefaultVrComponents
*/
List<ComponentName> getDefaultVrComponents();
+
+ /**
+ * @see SystemConfigManager#getPreventUserDisablePackages
+ */
+ List<String> getPreventUserDisablePackages();
}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d3f2c7ae6e42..eb5b511aa39b 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -94,4 +94,8 @@ per-file CoolingDevice.java = file:/THERMAL_OWNERS
per-file Temperature.java = file:/THERMAL_OWNERS
# SecurityStateManager
-per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS \ No newline at end of file
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
+
+# SystemConfig
+per-file ISystemConfig.aidl = file:/PACKAGE_MANAGER_OWNERS
+per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f71c2695c677..07f76904988a 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,8 +18,6 @@ package android.os;
import static android.view.Display.DEFAULT_DISPLAY;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -47,11 +45,8 @@ import android.text.format.DateFormat;
import android.util.Log;
import android.view.Display;
-import libcore.io.Streams;
-
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
@@ -75,7 +70,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -426,72 +420,43 @@ public class RecoverySystem {
} finally {
raf.close();
}
-
- // Additionally verify the package compatibility.
- if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
- throw new SignatureException("package compatibility verification failed");
- }
}
/**
* Verifies the compatibility entry from an {@link InputStream}.
*
- * @return the verification result.
+ * @param inputStream The stream that contains the package compatibility info.
+ * @throws IOException Never.
+ * @return {@code true}.
+ * @deprecated This function no longer checks {@code inputStream} and
+ * unconditionally returns true. Instead, check compatibility when the
+ * OTA package is generated.
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(
+ publicAlternatives = "Use {@code true} directly",
+ maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
- ArrayList<String> list = new ArrayList<>();
- ZipInputStream zis = new ZipInputStream(inputStream);
- ZipEntry entry;
- while ((entry = zis.getNextEntry()) != null) {
- long entrySize = entry.getSize();
- if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
- throw new IOException(
- "invalid entry size (" + entrySize + ") in the compatibility file");
- }
- byte[] bytes = new byte[(int) entrySize];
- Streams.readFully(zis, bytes);
- list.add(new String(bytes, UTF_8));
- }
- if (list.isEmpty()) {
- throw new IOException("no entries found in the compatibility file");
- }
- return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
- }
-
- /**
- * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
- * a zip file (inside the OTA package zip).
- *
- * @return {@code true} if the entry doesn't exist or verification passes.
- */
- private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
- throws IOException {
- try (ZipFile zip = new ZipFile(packageFile)) {
- ZipEntry entry = zip.getEntry("compatibility.zip");
- if (entry == null) {
- return true;
- }
- InputStream inputStream = zip.getInputStream(entry);
- return verifyPackageCompatibility(inputStream);
- }
+ return true;
}
/**
* Verifies the package compatibility info against the current system.
*
* @param compatibilityFile the {@link File} that contains the package compatibility info.
- * @throws IOException if there were any errors reading the compatibility file.
- * @return the compatibility verification result.
+ * @throws IOException Never.
+ * @return {@code true}
+ * @deprecated This function no longer checks {@code compatibilityFile} and
+ * unconditionally returns true. Instead, check compatibility when the
+ * OTA package is generated.
*
* {@hide}
*/
+ @Deprecated
@SystemApi
@SuppressLint("RequiresPermission")
public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
- try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
- return verifyPackageCompatibility(inputStream);
- }
+ return true;
}
/**
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 77843d9fbb0a..21ffbf18dbc3 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -161,4 +161,18 @@ public class SystemConfigManager {
}
return Collections.emptyList();
}
+
+ /**
+ * Return the packages that are prevented from being disabled, where if
+ * disabled it would result in a non-functioning system or similar.
+ * @hide
+ */
+ @NonNull
+ public List<String> getPreventUserDisablePackages() {
+ try {
+ return mInterface.getPreventUserDisablePackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1f11197afeee..4fc5131617b2 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -18,7 +18,6 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.util.Slog;
import java.util.Map;
@@ -44,44 +43,8 @@ public class VintfObject {
public static native String[] report();
/**
- * Verify that the given metadata for an OTA package is compatible with
- * this device.
- *
- * @param packageInfo a list of serialized form of HalManifest's /
- * CompatibilityMatri'ces (XML).
- * @return = 0 if success (compatible)
- * &gt; 0 if incompatible
- * &lt; 0 if any error (mount partition fails, illformed XML, etc.)
- *
- * @deprecated Checking compatibility against an OTA package is no longer
- * supported because the format of VINTF metadata in the OTA package may not
- * be recognized by the current system.
- *
- * <p>
- * <ul>
- * <li>This function always returns 0 for non-empty {@code packageInfo}.
- * </li>
- * <li>This function returns the result of {@link #verifyWithoutAvb} for
- * null or empty {@code packageInfo}.</li>
- * </ul>
- *
- * @hide
- */
- @Deprecated
- public static int verify(String[] packageInfo) {
- if (packageInfo != null && packageInfo.length > 0) {
- Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
- + "Skipping compatibility checks for update package.");
- return 0;
- }
- Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
- return verifyWithoutAvb();
- }
-
- /**
- * Verify Vintf compatibility on the device without checking AVB
- * (Android Verified Boot). It is useful to verify a running system
- * image where AVB check is irrelevant.
+ * Verify Vintf compatibility on the device at boot time. Certain checks
+ * like kernel checks, AVB checks are disabled.
*
* @return = 0 if success (compatible)
* > 0 if incompatible
@@ -89,7 +52,7 @@ public class VintfObject {
*
* @hide
*/
- public static native int verifyWithoutAvb();
+ public static native int verifyBuildAtBoot();
/**
* @return a list of HAL names and versions that is supported by this
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d09c2290b38a..fae7cb394906 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -64,3 +64,10 @@ flag {
description: "enable Permission PREPARE_FACTORY_RESET."
bug: "302016478"
}
+
+flag {
+ name: "retail_demo_role_enabled"
+ namespace: "permissions"
+ description: "default retail demo role holder"
+ bug: "274132354"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 84fddcb2c22a..e8da0d9341d3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -108,10 +108,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -3585,10 +3585,12 @@ public final class Settings {
|| applicationInfo.isSignedWithPlatformKey();
}
- public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
- List<String> names) {
+ private ArrayMap<String, String> getStringsForPrefixStripPrefix(
+ ContentResolver cr, String prefix, 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;
@@ -3601,22 +3603,30 @@ public final class Settings {
+ " type:" + mUri.getPath()
+ " in package:" + cr.getPackageName());
}
+ // When a generation number changes, remove cached values, remove the old
+ // 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);
}
}
+ needsGenerationTracker = true;
} else {
boolean prefixCached = mValues.containsKey(prefix);
if (prefixCached) {
if (DEBUG) {
Log.i(TAG, "Cache hit for prefix:" + prefix);
}
- if (!names.isEmpty()) {
+ if (names.length > 0) {
for (String name : names) {
- if (mValues.containsKey(name)) {
- keyValues.put(name, mValues.get(name));
+ String value = mValues.get(name);
+ if (value != null) {
+ keyValues.put(
+ name.substring(substringLength),
+ value);
}
}
} else {
@@ -3625,7 +3635,10 @@ public final class Settings {
// Explicitly exclude the prefix as it is only there to
// signal that the prefix has been cached.
if (key.startsWith(prefix) && !key.equals(prefix)) {
- keyValues.put(key, mValues.get(key));
+ String value = mValues.valueAt(i);
+ keyValues.put(
+ key.substring(substringLength),
+ value);
}
}
}
@@ -3685,14 +3698,22 @@ public final class Settings {
Map<String, String> flagsToValues =
(HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
// Only the flags requested by the caller
- if (!names.isEmpty()) {
- for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
- if (names.contains(flag.getKey())) {
- keyValues.put(flag.getKey(), flag.getValue());
+ if (names.length > 0) {
+ for (String name : names) {
+ String value = flagsToValues.get(name);
+ if (value != null) {
+ keyValues.put(
+ name.substring(substringLength),
+ value);
}
}
} else {
- keyValues.putAll(flagsToValues);
+ keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
+ for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+ keyValues.put(
+ flag.getKey().substring(substringLength),
+ flag.getValue());
+ }
}
synchronized (NameValueCache.this) {
@@ -12172,6 +12193,7 @@ public final class Settings {
CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
+ CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
}
/** @hide */
@@ -19674,6 +19696,15 @@ public final class Settings {
@Readable
public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
"wear_wrist_detection_auto_locking_enabled";
+
+ /**
+ * Whether consistent notification blocking experience is enabled.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
+ "consistent_notification_blocking_enabled";
}
}
@@ -19834,21 +19865,15 @@ 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) {
- List<String> compositeNames = new ArrayList<>(names.size());
- for (String name : names) {
- compositeNames.add(createCompositeName(namespace, name));
+ 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> rawKeyValues = sNameValueCache.getStringsForPrefix(
+
+ ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
resolver, prefix, compositeNames);
- int size = rawKeyValues.size();
- int substringLength = prefix.length();
- ArrayMap<String, String> keyValues = new ArrayMap<>(size);
- for (int i = 0; i < size; ++i) {
- keyValues.put(rawKeyValues.keyAt(i).substring(substringLength),
- rawKeyValues.valueAt(i));
- }
return keyValues;
}
@@ -20174,12 +20199,13 @@ public final class Settings {
private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
Preconditions.checkNotNull(namespace);
Preconditions.checkNotNull(name);
- return createPrefix(namespace) + name;
+ var sb = new StringBuilder(namespace.length() + 1 + name.length());
+ return sb.append(namespace).append('/').append(name).toString();
}
private static String createPrefix(@NonNull String namespace) {
Preconditions.checkNotNull(namespace);
- return namespace + "/";
+ return namespace + '/';
}
private static Uri createNamespaceUri(@NonNull String namespace) {
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 0e82b6c2c7d7..03ebae5c5199 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -17,12 +17,16 @@
package android.service.notification;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
@@ -33,6 +37,76 @@ import java.util.Objects;
@FlaggedApi(Flags.FLAG_MODES_API)
public final class ZenDeviceEffects implements Parcelable {
+ /** Used to track which rule variables have been modified by the user.
+ * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FIELD_" }, value = {
+ FIELD_GRAYSCALE,
+ FIELD_SUPPRESS_AMBIENT_DISPLAY,
+ FIELD_DIM_WALLPAPER,
+ FIELD_NIGHT_MODE,
+ FIELD_DISABLE_AUTO_BRIGHTNESS,
+ FIELD_DISABLE_TAP_TO_WAKE,
+ FIELD_DISABLE_TILT_TO_WAKE,
+ FIELD_DISABLE_TOUCH,
+ FIELD_MINIMIZE_RADIO_USAGE,
+ FIELD_MAXIMIZE_DOZE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModifiableField {}
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_GRAYSCALE = 1 << 0;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_DIM_WALLPAPER = 1 << 2;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_NIGHT_MODE = 1 << 3;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_DISABLE_TOUCH = 1 << 7;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8;
+ /**
+ * @hide
+ */
+ @TestApi
+ public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
+
private final boolean mGrayscale;
private final boolean mSuppressAmbientDisplay;
private final boolean mDimWallpaper;
@@ -45,10 +119,13 @@ public final class ZenDeviceEffects implements Parcelable {
private final boolean mMinimizeRadioUsage;
private final boolean mMaximizeDoze;
+ private final @ModifiableField int mUserModifiedFields; // Bitwise representation
+
private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
- boolean minimizeRadioUsage, boolean maximizeDoze) {
+ boolean minimizeRadioUsage, boolean maximizeDoze,
+ @ModifiableField int userModifiedFields) {
mGrayscale = grayscale;
mSuppressAmbientDisplay = suppressAmbientDisplay;
mDimWallpaper = dimWallpaper;
@@ -59,6 +136,7 @@ public final class ZenDeviceEffects implements Parcelable {
mDisableTouch = disableTouch;
mMinimizeRadioUsage = minimizeRadioUsage;
mMaximizeDoze = maximizeDoze;
+ mUserModifiedFields = userModifiedFields;
}
@Override
@@ -75,14 +153,15 @@ public final class ZenDeviceEffects implements Parcelable {
&& this.mDisableTiltToWake == that.mDisableTiltToWake
&& this.mDisableTouch == that.mDisableTouch
&& this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
- && this.mMaximizeDoze == that.mMaximizeDoze;
+ && this.mMaximizeDoze == that.mMaximizeDoze
+ && this.mUserModifiedFields == that.mUserModifiedFields;
}
@Override
public int hashCode() {
return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
- mMinimizeRadioUsage, mMaximizeDoze);
+ mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields);
}
@Override
@@ -98,7 +177,43 @@ public final class ZenDeviceEffects implements Parcelable {
if (mDisableTouch) effects.add("disableTouch");
if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
if (mMaximizeDoze) effects.add("maximizeDoze");
- return "[" + String.join(", ", effects) + "]";
+ return "[" + String.join(", ", effects) + "]"
+ + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields);
+ }
+
+ private String modifiedFieldsToString(int bitmask) {
+ ArrayList<String> modified = new ArrayList<>();
+ if ((bitmask & FIELD_GRAYSCALE) != 0) {
+ modified.add("FIELD_GRAYSCALE");
+ }
+ if ((bitmask & FIELD_SUPPRESS_AMBIENT_DISPLAY) != 0) {
+ modified.add("FIELD_SUPPRESS_AMBIENT_DISPLAY");
+ }
+ if ((bitmask & FIELD_DIM_WALLPAPER) != 0) {
+ modified.add("FIELD_DIM_WALLPAPER");
+ }
+ if ((bitmask & FIELD_NIGHT_MODE) != 0) {
+ modified.add("FIELD_NIGHT_MODE");
+ }
+ if ((bitmask & FIELD_DISABLE_AUTO_BRIGHTNESS) != 0) {
+ modified.add("FIELD_DISABLE_AUTO_BRIGHTNESS");
+ }
+ if ((bitmask & FIELD_DISABLE_TAP_TO_WAKE) != 0) {
+ modified.add("FIELD_DISABLE_TAP_TO_WAKE");
+ }
+ if ((bitmask & FIELD_DISABLE_TILT_TO_WAKE) != 0) {
+ modified.add("FIELD_DISABLE_TILT_TO_WAKE");
+ }
+ if ((bitmask & FIELD_DISABLE_TOUCH) != 0) {
+ modified.add("FIELD_DISABLE_TOUCH");
+ }
+ if ((bitmask & FIELD_MINIMIZE_RADIO_USAGE) != 0) {
+ modified.add("FIELD_MINIMIZE_RADIO_USAGE");
+ }
+ if ((bitmask & FIELD_MAXIMIZE_DOZE) != 0) {
+ modified.add("FIELD_MAXIMIZE_DOZE");
+ }
+ return "{" + String.join(",", modified) + "}";
}
/**
@@ -194,9 +309,10 @@ public final class ZenDeviceEffects implements Parcelable {
public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() {
@Override
public ZenDeviceEffects createFromParcel(Parcel in) {
- return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(),
+ return new ZenDeviceEffects(in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
- in.readBoolean(), in.readBoolean(), in.readBoolean());
+ in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
+ in.readBoolean(), in.readInt());
}
@Override
@@ -205,6 +321,16 @@ public final class ZenDeviceEffects implements Parcelable {
}
};
+ /**
+ * Gets the bitmask representing which fields are user modified. Bits are set using
+ * {@link ModifiableField}.
+ * @hide
+ */
+ @TestApi
+ public @ModifiableField int getUserModifiedFields() {
+ return mUserModifiedFields;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -222,6 +348,7 @@ public final class ZenDeviceEffects implements Parcelable {
dest.writeBoolean(mDisableTouch);
dest.writeBoolean(mMinimizeRadioUsage);
dest.writeBoolean(mMaximizeDoze);
+ dest.writeInt(mUserModifiedFields);
}
/** Builder class for {@link ZenDeviceEffects} objects. */
@@ -238,6 +365,7 @@ public final class ZenDeviceEffects implements Parcelable {
private boolean mDisableTouch;
private boolean mMinimizeRadioUsage;
private boolean mMaximizeDoze;
+ private @ModifiableField int mUserModifiedFields;
/**
* Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -260,6 +388,7 @@ public final class ZenDeviceEffects implements Parcelable {
mDisableTouch = zenDeviceEffects.shouldDisableTouch();
mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
+ mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
}
/**
@@ -381,12 +510,24 @@ public final class ZenDeviceEffects implements Parcelable {
return this;
}
+ /**
+ * Sets the bitmask representing which fields are user modified. See the FIELD_ constants.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
+ mUserModifiedFields = userModifiedFields;
+ return this;
+ }
+
/** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
@NonNull
public ZenDeviceEffects build() {
- return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper,
- mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake,
- mDisableTouch, mMinimizeRadioUsage, mMaximizeDoze);
+ return new ZenDeviceEffects(mGrayscale,
+ mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
+ mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
+ mMaximizeDoze, mUserModifiedFields);
}
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index fcdc5fe71e4e..45a0c205a09b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -205,6 +205,7 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_CONV = "convos";
private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
private static final String ALLOW_ATT_CHANNELS = "channels";
+ private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
private static final String STATE_TAG = "state";
@@ -247,6 +248,7 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_MODIFIED = "modified";
private static final String RULE_ATT_ALLOW_MANUAL = "userInvokable";
private static final String RULE_ATT_TYPE = "type";
+ private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
private static final String RULE_ATT_ICON = "rule_icon";
private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
@@ -261,6 +263,7 @@ public class ZenModeConfig implements Parcelable {
private static final String DEVICE_EFFECT_DISABLE_TOUCH = "zdeDisableTouch";
private static final String DEVICE_EFFECT_MINIMIZE_RADIO_USAGE = "zdeMinimizeRadioUsage";
private static final String DEVICE_EFFECT_MAXIMIZE_DOZE = "zdeMaximizeDoze";
+ private static final String DEVICE_EFFECT_USER_MODIFIED_FIELDS = "zdeUserModifiedFields";
@UnsupportedAppUsage
public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
@@ -748,6 +751,7 @@ public class ZenModeConfig implements Parcelable {
rt.iconResName = parser.getAttributeValue(null, RULE_ATT_ICON);
rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
+ rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
}
return rt;
}
@@ -794,6 +798,7 @@ public class ZenModeConfig implements Parcelable {
out.attribute(null, RULE_ATT_TRIGGER_DESC, rule.triggerDescription);
}
out.attributeInt(null, RULE_ATT_TYPE, rule.type);
+ out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
}
}
@@ -856,6 +861,7 @@ public class ZenModeConfig implements Parcelable {
builder.allowChannels(channels);
policySet = true;
}
+ builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0));
}
if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -968,6 +974,7 @@ public class ZenModeConfig implements Parcelable {
if (Flags.modesApi()) {
writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getAllowedChannels(), out);
+ out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
}
}
@@ -993,6 +1000,7 @@ public class ZenModeConfig implements Parcelable {
}
}
+ @FlaggedApi(Flags.FLAG_MODES_API)
@Nullable
private static ZenDeviceEffects readZenDeviceEffectsXml(TypedXmlPullParser parser) {
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
@@ -1012,11 +1020,13 @@ public class ZenModeConfig implements Parcelable {
.setShouldMinimizeRadioUsage(
safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
.setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
+ .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0))
.build();
return deviceEffects.hasEffects() ? deviceEffects : null;
}
+ @FlaggedApi(Flags.FLAG_MODES_API)
private static void writeZenDeviceEffectsXml(ZenDeviceEffects deviceEffects,
TypedXmlSerializer out) throws IOException {
writeBooleanIfTrue(out, DEVICE_EFFECT_DISPLAY_GRAYSCALE,
@@ -1035,6 +1045,8 @@ public class ZenModeConfig implements Parcelable {
writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
deviceEffects.shouldMinimizeRadioUsage());
writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
+ out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+ deviceEffects.getUserModifiedFields());
}
private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -1985,6 +1997,7 @@ public class ZenModeConfig implements Parcelable {
public String triggerDescription;
public String iconResName;
public boolean allowManualInvocation;
+ public int userModifiedFields;
public ZenRule() { }
@@ -2017,9 +2030,22 @@ public class ZenModeConfig implements Parcelable {
iconResName = source.readString();
triggerDescription = source.readString();
type = source.readInt();
+ userModifiedFields = source.readInt();
}
}
+ /**
+ * @see AutomaticZenRule#canUpdate()
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean canBeUpdatedByApp() {
+ // The rule is considered updateable if its bitmask has no user modifications, and
+ // the bitmasks of the policy and device effects have no modification.
+ return userModifiedFields == 0
+ && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0)
+ && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -2064,6 +2090,7 @@ public class ZenModeConfig implements Parcelable {
dest.writeString(iconResName);
dest.writeString(triggerDescription);
dest.writeInt(type);
+ dest.writeInt(userModifiedFields);
}
}
@@ -2092,7 +2119,8 @@ public class ZenModeConfig implements Parcelable {
.append(",allowManualInvocation=").append(allowManualInvocation)
.append(",iconResName=").append(iconResName)
.append(",triggerDescription=").append(triggerDescription)
- .append(",type=").append(type);
+ .append(",type=").append(type)
+ .append(",userModifiedFields=").append(userModifiedFields);
}
return sb.append(']').toString();
@@ -2151,7 +2179,8 @@ public class ZenModeConfig implements Parcelable {
&& other.allowManualInvocation == allowManualInvocation
&& Objects.equals(other.iconResName, iconResName)
&& Objects.equals(other.triggerDescription, triggerDescription)
- && other.type == type;
+ && other.type == type
+ && other.userModifiedFields == userModifiedFields;
}
return finalEquals;
@@ -2163,7 +2192,7 @@ public class ZenModeConfig implements Parcelable {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
zenDeviceEffects, modified, allowManualInvocation, iconResName,
- triggerDescription, type);
+ triggerDescription, type, userModifiedFields);
}
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index d87e75884802..8902368072bf 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -467,6 +467,7 @@ public class ZenModeDiff {
public static final String FIELD_ICON_RES = "iconResName";
public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
public static final String FIELD_TYPE = "type";
+ public static final String FIELD_USER_MODIFIED_FIELDS = "userModifiedFields";
// NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
// Special field to track whether this rule became active or inactive
@@ -562,6 +563,10 @@ public class ZenModeDiff {
if (!Objects.equals(from.iconResName, to.iconResName)) {
addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
}
+ if (from.userModifiedFields != to.userModifiedFields) {
+ addField(FIELD_USER_MODIFIED_FIELDS,
+ new FieldDiff<>(from.userModifiedFields, to.userModifiedFields));
+ }
}
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 3c1a2796de95..8477eb7120c2 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -20,6 +20,8 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.app.Flags;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -32,6 +34,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -41,12 +44,148 @@ import java.util.Objects;
* a device is in Do Not Disturb mode.
*/
public final class ZenPolicy implements Parcelable {
- private ArrayList<Integer> mPriorityCategories;
- private ArrayList<Integer> mVisualEffects;
+
+ /** Used to track which rule variables have been modified by the user.
+ * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ * @hide
+ */
+ @IntDef(flag = true, prefix = { "FIELD_" }, value = {
+ FIELD_MESSAGES,
+ FIELD_CALLS,
+ FIELD_CONVERSATIONS,
+ FIELD_ALLOW_CHANNELS,
+ FIELD_PRIORITY_CATEGORY_REMINDERS,
+ FIELD_PRIORITY_CATEGORY_EVENTS,
+ FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS,
+ FIELD_PRIORITY_CATEGORY_ALARMS,
+ FIELD_PRIORITY_CATEGORY_MEDIA,
+ FIELD_PRIORITY_CATEGORY_SYSTEM,
+ FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT,
+ FIELD_VISUAL_EFFECT_LIGHTS,
+ FIELD_VISUAL_EFFECT_PEEK,
+ FIELD_VISUAL_EFFECT_STATUS_BAR,
+ FIELD_VISUAL_EFFECT_BADGE,
+ FIELD_VISUAL_EFFECT_AMBIENT,
+ FIELD_VISUAL_EFFECT_NOTIFICATION_LIST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ModifiableField {}
+
+ /**
+ * Covers modifications to MESSAGE_SENDERS and PRIORITY_CATEGORY_MESSAGES, which are set at
+ * the same time.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_MESSAGES = 1 << 0;
+ /**
+ * Covers modifications to CALL_SENDERS and PRIORITY_CATEGORY_CALLS, which are set at
+ * the same time.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_CALLS = 1 << 1;
+ /**
+ * Covers modifications to CONVERSATION_SENDERS and PRIORITY_CATEGORY_CONVERSATIONS, which are
+ * set at the same time.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_CONVERSATIONS = 1 << 2;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_REMINDERS = 1 << 4;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
+ /**
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
+
+ private List<Integer> mPriorityCategories;
+ private List<Integer> mVisualEffects;
private @PeopleType int mPriorityMessages = PEOPLE_TYPE_UNSET;
private @PeopleType int mPriorityCalls = PEOPLE_TYPE_UNSET;
private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
private @ChannelType int mAllowChannels = CHANNEL_TYPE_UNSET;
+ private final @ModifiableField int mUserModifiedFields; // Bitwise representation
/** @hide */
@IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -249,6 +388,22 @@ public final class ZenPolicy implements Parcelable {
public ZenPolicy() {
mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
+ mUserModifiedFields = 0;
+ }
+
+ /** @hide */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
+ @PeopleType int priorityMessages, @PeopleType int priorityCalls,
+ @ConversationSenders int conversationSenders, @ChannelType int allowChannels,
+ @ModifiableField int userModifiedFields) {
+ mPriorityCategories = priorityCategories;
+ mVisualEffects = visualEffects;
+ mPriorityMessages = priorityMessages;
+ mPriorityCalls = priorityCalls;
+ mConversationSenders = conversationSenders;
+ mAllowChannels = allowChannels;
+ mUserModifiedFields = userModifiedFields;
}
/**
@@ -473,6 +628,8 @@ public final class ZenPolicy implements Parcelable {
* is not set, it is (@link STATE_UNSET} and will not change the current set policy.
*/
public static final class Builder {
+ private @ModifiableField int mUserModifiedFields;
+
private ZenPolicy mZenPolicy;
public Builder() {
@@ -482,9 +639,14 @@ public final class ZenPolicy implements Parcelable {
/**
* @hide
*/
- public Builder(ZenPolicy policy) {
+ @SuppressLint("UnflaggedApi")
+ @TestApi
+ public Builder(@Nullable ZenPolicy policy) {
if (policy != null) {
mZenPolicy = policy.copy();
+ if (Flags.modesApi()) {
+ mUserModifiedFields = policy.mUserModifiedFields;
+ }
} else {
mZenPolicy = new ZenPolicy();
}
@@ -494,7 +656,15 @@ public final class ZenPolicy implements Parcelable {
* Builds the current ZenPolicy.
*/
public @NonNull ZenPolicy build() {
- return mZenPolicy.copy();
+ if (Flags.modesApi()) {
+ return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories),
+ new ArrayList<Integer>(mZenPolicy.mVisualEffects),
+ mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
+ mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels,
+ mUserModifiedFields);
+ } else {
+ return mZenPolicy.copy();
+ }
}
/**
@@ -850,6 +1020,28 @@ public final class ZenPolicy implements Parcelable {
mZenPolicy.mAllowChannels = channelType;
return this;
}
+
+ /**
+ * Sets the user modified fields bitmask.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
+ mUserModifiedFields = userModifiedFields;
+ return this;
+ }
+ }
+
+ /**
+ Gets the bitmask representing which fields are user modified. Bits are set using
+ * {@link ModifiableField}.
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public @ModifiableField int getUserModifiedFields() {
+ return mUserModifiedFields;
}
@Override
@@ -861,39 +1053,49 @@ public final class ZenPolicy implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeList(mPriorityCategories);
dest.writeList(mVisualEffects);
- dest.writeInt(mPriorityCalls);
dest.writeInt(mPriorityMessages);
+ dest.writeInt(mPriorityCalls);
dest.writeInt(mConversationSenders);
if (Flags.modesApi()) {
dest.writeInt(mAllowChannels);
+ dest.writeInt(mUserModifiedFields);
}
}
- public static final @android.annotation.NonNull Parcelable.Creator<ZenPolicy> CREATOR =
- new Parcelable.Creator<ZenPolicy>() {
- @Override
- public ZenPolicy createFromParcel(Parcel source) {
- ZenPolicy policy = new ZenPolicy();
- policy.mPriorityCategories = trimList(
- source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
- NUM_PRIORITY_CATEGORIES);
- policy.mVisualEffects = trimList(
- source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
- NUM_VISUAL_EFFECTS);
- policy.mPriorityCalls = source.readInt();
- policy.mPriorityMessages = source.readInt();
- policy.mConversationSenders = source.readInt();
- if (Flags.modesApi()) {
- policy.mAllowChannels = source.readInt();
- }
- return policy;
- }
+ public static final @NonNull Creator<ZenPolicy> CREATOR =
+ new Creator<ZenPolicy>() {
+ @Override
+ public ZenPolicy createFromParcel(Parcel source) {
+ ZenPolicy policy;
+ if (Flags.modesApi()) {
+ policy = new ZenPolicy(
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_PRIORITY_CATEGORIES),
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_VISUAL_EFFECTS),
+ source.readInt(), source.readInt(), source.readInt(),
+ source.readInt(), source.readInt()
+ );
+ } else {
+ policy = new ZenPolicy();
+ policy.mPriorityCategories =
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_PRIORITY_CATEGORIES);
+ policy.mVisualEffects =
+ trimList(source.readArrayList(Integer.class.getClassLoader(),
+ Integer.class), NUM_VISUAL_EFFECTS);
+ policy.mPriorityMessages = source.readInt();
+ policy.mPriorityCalls = source.readInt();
+ policy.mConversationSenders = source.readInt();
+ }
+ return policy;
+ }
- @Override
- public ZenPolicy[] newArray(int size) {
- return new ZenPolicy[size];
- }
- };
+ @Override
+ public ZenPolicy[] newArray(int size) {
+ return new ZenPolicy[size];
+ }
+ };
@Override
public String toString() {
@@ -907,10 +1109,69 @@ public final class ZenPolicy implements Parcelable {
conversationTypeToString(mConversationSenders));
if (Flags.modesApi()) {
sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
+ sb.append(", userModifiedFields=")
+ .append(modifiedFieldsToString(mUserModifiedFields));
}
return sb.append('}').toString();
}
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ private String modifiedFieldsToString(@ModifiableField int bitmask) {
+ ArrayList<String> modified = new ArrayList<>();
+ if ((bitmask & FIELD_MESSAGES) != 0) {
+ modified.add("FIELD_MESSAGES");
+ }
+ if ((bitmask & FIELD_CALLS) != 0) {
+ modified.add("FIELD_CALLS");
+ }
+ if ((bitmask & FIELD_CONVERSATIONS) != 0) {
+ modified.add("FIELD_CONVERSATIONS");
+ }
+ if ((bitmask & FIELD_ALLOW_CHANNELS) != 0) {
+ modified.add("FIELD_ALLOW_CHANNELS");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_REMINDERS) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_REMINDERS");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_EVENTS) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_EVENTS");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_ALARMS) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_ALARMS");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_MEDIA) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_MEDIA");
+ }
+ if ((bitmask & FIELD_PRIORITY_CATEGORY_SYSTEM) != 0) {
+ modified.add("FIELD_PRIORITY_CATEGORY_SYSTEM");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_LIGHTS) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_LIGHTS");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_PEEK) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_PEEK");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_STATUS_BAR) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_STATUS_BAR");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_BADGE) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_BADGE");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_AMBIENT) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_AMBIENT");
+ }
+ if ((bitmask & FIELD_VISUAL_EFFECT_NOTIFICATION_LIST) != 0) {
+ modified.add("FIELD_VISUAL_EFFECT_NOTIFICATION_LIST");
+ }
+ return "{" + String.join(",", modified) + "}";
+ }
+
// Returns a list containing the first maxLength elements of the input list if the list is
// longer than that size. For the lists in ZenPolicy, this should not happen unless the input
// is corrupt.
@@ -1066,7 +1327,8 @@ public final class ZenPolicy implements Parcelable {
&& other.mPriorityMessages == mPriorityMessages
&& other.mConversationSenders == mConversationSenders;
if (Flags.modesApi()) {
- return eq && other.mAllowChannels == mAllowChannels;
+ return eq && other.mAllowChannels == mAllowChannels
+ && other.mUserModifiedFields == mUserModifiedFields;
}
return eq;
}
@@ -1075,13 +1337,13 @@ public final class ZenPolicy implements Parcelable {
public int hashCode() {
if (Flags.modesApi()) {
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
- mPriorityMessages, mConversationSenders, mAllowChannels);
+ mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields);
}
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
mConversationSenders);
}
- private @ZenPolicy.State int getZenPolicyPriorityCategoryState(@PriorityCategory int
+ private @State int getZenPolicyPriorityCategoryState(@PriorityCategory int
category) {
switch (category) {
case PRIORITY_CATEGORY_REMINDERS:
@@ -1106,7 +1368,7 @@ public final class ZenPolicy implements Parcelable {
return -1;
}
- private @ZenPolicy.State int getZenPolicyVisualEffectState(@VisualEffect int effect) {
+ private @State int getZenPolicyVisualEffectState(@VisualEffect int effect) {
switch (effect) {
case VISUAL_EFFECT_FULL_SCREEN_INTENT:
return getVisualEffectFullScreenIntent();
diff --git a/core/java/android/view/DisplayAddress.java b/core/java/android/view/DisplayAddress.java
index 99e811a72605..c3c4ab52d5ef 100644
--- a/core/java/android/view/DisplayAddress.java
+++ b/core/java/android/view/DisplayAddress.java
@@ -138,6 +138,30 @@ public abstract class DisplayAddress implements Parcelable {
out.writeLong(mPhysicalDisplayId);
}
+ /**
+ * This method is meant to check to see if the ports match
+ * @param a1 Address to compare
+ * @param a2 Address to compare
+ *
+ * @return true if the arguments have the same port, and at least one does not specify
+ * a model.
+ */
+ public static boolean isPortMatch(DisplayAddress a1, DisplayAddress a2) {
+ // Both displays must be of type Physical
+ if (!(a1 instanceof Physical && a2 instanceof Physical)) {
+ return false;
+ }
+ Physical p1 = (Physical) a1;
+ Physical p2 = (Physical) a2;
+
+ // If both addresses specify a model, fallback to a basic match check (which
+ // also checks the port).
+ if (p1.getModel() != null && p2.getModel() != null) {
+ return p1.equals(p2);
+ }
+ return p1.getPort() == p2.getPort();
+ }
+
private Physical(long physicalDisplayId) {
mPhysicalDisplayId = physicalDisplayId;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8529b4e044fa..350876c828b7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6943,7 +6943,11 @@ public final class ViewRootImpl implements ViewParent,
}
private int doOnBackKeyEvent(KeyEvent keyEvent) {
- OnBackInvokedCallback topCallback = getOnBackInvokedDispatcher().getTopCallback();
+ WindowOnBackInvokedDispatcher dispatcher = getOnBackInvokedDispatcher();
+ OnBackInvokedCallback topCallback = dispatcher.getTopCallback();
+ if (dispatcher.isDispatching()) {
+ return FINISH_NOT_HANDLED;
+ }
if (topCallback instanceof OnBackAnimationCallback) {
final OnBackAnimationCallback animationCallback =
(OnBackAnimationCallback) topCallback;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 61cf1266177a..b4ac9a22aad3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1494,6 +1494,30 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
/**
+ * Activity or Application level {@link android.content.pm.PackageManager.Property
+ * PackageManager.Property} for an app to declare that System UI should be shown for this
+ * app/component to allow it to be launched as multiple instances. This property only affects
+ * SystemUI behavior and does _not_ affect whether a component can actually be launched into
+ * multiple instances, which is determined by the Activity's {@code launchMode} or the launching
+ * Intent's flags. If the property is set on the Application, then all components within that
+ * application will use that value unless specified per component.
+ *
+ * The value must be a boolean string.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
+ * android:value="true|false"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ */
+ @FlaggedApi(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
+ public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI =
+ "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+
+ /**
* Request for app's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
@@ -3163,6 +3187,12 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
/**
+ * Flag to indicate that the window is forcibly to go edge-to-edge.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED = 1 << 11;
+
+ /**
* Flag to indicate that the window frame should be the requested frame adding the display
* cutout frame. This will only be applied if a specific size smaller than the parent frame
* is given, and the window is covering the display cutout. The extended frame will not be
@@ -3338,6 +3368,7 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3400,6 +3431,10 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+ equals = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+ name = "EDGE_TO_EDGE_ENFORCED"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index feccc6bef7a4..3bc02a6c8fd1 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -354,7 +354,11 @@ public final class InputMethodManager {
* @hide
*/
public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
- forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+ // Skip this call if we are in system_server, as the system code should not use this
+ // deprecated instance.
+ if (!ActivityThread.isSystem()) {
+ forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+ }
}
private static final Object sLock = new Object();
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 6a8ca339d60d..86804c6117c7 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -174,6 +174,21 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
+ /**
+ * Indicates if the dispatcher is actively dispatching to a callback.
+ */
+ public boolean isDispatching() {
+ return mIsDispatching;
+ }
+
+ private void onStartDispatching() {
+ mIsDispatching = true;
+ }
+
+ private void onStopDispatching() {
+ mIsDispatching = false;
+ }
+
private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
if (isInProgress && callback instanceof OnBackAnimationCallback) {
@@ -231,7 +246,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback);
+ : new OnBackInvokedCallbackWrapper(callback, this);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
@@ -258,6 +273,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@NonNull
private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ private boolean mIsDispatching = false;
/**
* The {@link Context} in ViewRootImp and Activity could be different, this will make sure it
@@ -317,18 +333,33 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
final CallbackRef mCallbackRef;
+ /**
+ * The dispatcher this callback is registered with.
+ * This can be null for callbacks on {@link ImeOnBackInvokedDispatcher} because they are
+ * forwarded and registered on the app's {@link WindowOnBackInvokedDispatcher}. */
+ @Nullable
+ private final WindowOnBackInvokedDispatcher mDispatcher;
- OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
+ OnBackInvokedCallbackWrapper(
+ @NonNull OnBackInvokedCallback callback,
+ WindowOnBackInvokedDispatcher dispatcher) {
mCallbackRef = new CallbackRef(callback, true /* useWeakRef */);
+ mDispatcher = dispatcher;
}
- OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback, boolean useWeakRef) {
+ OnBackInvokedCallbackWrapper(
+ @NonNull OnBackInvokedCallback callback,
+ boolean useWeakRef) {
mCallbackRef = new CallbackRef(callback, useWeakRef);
+ mDispatcher = null;
}
@Override
public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
+ if (mDispatcher != null) {
+ mDispatcher.onStartDispatching();
+ }
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
@@ -353,6 +384,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackCancelled() {
Handler.getMain().post(() -> {
+ if (mDispatcher != null) {
+ mDispatcher.onStopDispatching();
+ }
mProgressAnimator.onBackCancelled(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
@@ -365,6 +399,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> {
+ if (mDispatcher != null) {
+ mDispatcher.onStopDispatching();
+ }
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallbackRef.get();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3366a7ee23c0..f2bce9c44001 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -82,4 +82,12 @@ flag {
description: "Enable record activity snapshot by default"
bug: "259497289"
is_fixed_read_only: true
+}
+
+flag {
+ name: "supports_multi_instance_system_ui"
+ namespace: "multitasking"
+ description: "Feature flag to enable a multi-instance system ui component property."
+ bug: "262864589"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 1bd098291a2a..eeea17bf39dd 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -68,10 +68,10 @@ public class SystemUiSystemPropertiesFlags {
// TODO b/291899544: for released flags, use resource config values
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t1", 5000);
+ "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t2", 3000);
+ "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_VOLUME1 = devFlag(
"persist.debug.sysui.notification.notif_volume1", 30);
@@ -80,12 +80,6 @@ public class SystemUiSystemPropertiesFlags {
/** Value used by polite notif. feature. -1 to ignore the counter */
public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
"persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
- /**
- * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1,
- * rule2
- */
- public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 31910ac7fce5..4e3b64c0d4b9 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATIO
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -46,6 +47,7 @@ import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
@@ -294,9 +296,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private int mFrameResource = 0;
private int mTextColor = 0;
- int mStatusBarColor = 0;
- int mNavigationBarColor = 0;
- int mNavigationBarDividerColor = 0;
+ int mStatusBarColor = Color.TRANSPARENT;
+ int mNavigationBarColor = Color.TRANSPARENT;
+ int mNavigationBarDividerColor = Color.TRANSPARENT;
private boolean mForcedStatusBarColor = false;
private boolean mForcedNavigationBarColor = false;
@@ -388,11 +390,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
- mEdgeToEdgeEnforced =
- context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
- || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
- && Flags.enforceEdgeToEdge());
+ mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(context.getApplicationInfo(), true /* local */);
if (mEdgeToEdgeEnforced) {
+ getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
mDecorFitsSystemWindows = false;
}
}
@@ -431,6 +431,22 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mActivityConfigCallback = activityConfigCallback;
}
+ /**
+ * Returns whether the given application is enforced to go edge-to-edge.
+ *
+ * @param info The application to query.
+ * @param local Whether this is called from the process of the given application.
+ * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
+ */
+ public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local) {
+ return info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+ || (Flags.enforceEdgeToEdge() && (local
+ // Calling this doesn't require a permission.
+ ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+ // Calling this requires permissions.
+ : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)));
+ }
+
@Override
public final void setContainer(Window container) {
super.setContainer(container);
@@ -2548,17 +2564,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
- if (!mForcedStatusBarColor) {
- final int statusBarCompatibleColor = context.getColor(R.color.status_bar_compatible);
- final int statusBarDefaultColor = context.getColor(R.color.status_bar_default);
- final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
- statusBarDefaultColor);
-
- mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
- ? statusBarCompatibleColor
- : statusBarColor;
+ if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
+ mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
}
- if (!mForcedNavigationBarColor) {
+ if (!mForcedNavigationBarColor && !mEdgeToEdgeEnforced) {
final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
@@ -2566,7 +2575,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mNavigationBarColor =
navBarColor == navBarDefaultColor
- && !mEdgeToEdgeEnforced
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
? navBarCompatibleColor
@@ -2575,7 +2583,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
Color.TRANSPARENT);
}
- if (!targetPreQ) {
+ if (!targetPreQ && !mEdgeToEdgeEnforced) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3899,6 +3907,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setStatusBarColor(int color) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
if (mStatusBarColor == color && mForcedStatusBarColor) {
return;
}
@@ -3920,6 +3931,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setNavigationBarColor(int color) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
if (mNavigationBarColor == color && mForcedNavigationBarColor) {
return;
}
@@ -3936,6 +3950,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setNavigationBarDividerColor(int navigationBarDividerColor) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mNavigationBarDividerColor = navigationBarDividerColor;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3949,6 +3966,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setStatusBarContrastEnforced(boolean ensureContrast) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mEnsureStatusBarContrastWhenTransparent = ensureContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -3962,6 +3982,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setNavigationBarContrastEnforced(boolean enforceContrast) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
@@ -4031,6 +4054,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
mDecorFitsSystemWindows = decorFitsSystemWindows;
applyDecorFitsSystemWindows();
}
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
deleted file mode 100644
index 1c2d19d94871..000000000000
--- a/core/java/com/android/server/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 893cc98ff9b1..6f1c76385120 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -82,10 +82,16 @@ struct SQLiteConnection {
const String8 path;
const String8 label;
+ // The prepared statement used to determine which tables are updated by a statement. This
+ // is is initially null. It is set non-null on first use.
+ sqlite3_stmt* tableQuery;
+
volatile bool canceled;
SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
- db(db), openFlags(openFlags), path(path), label(label), canceled(false) { }
+ db(db), openFlags(openFlags), path(path), label(label), tableQuery(nullptr),
+ canceled(false) { }
+
};
// Called each time a statement begins execution, when tracing is enabled.
@@ -188,6 +194,9 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
if (connection) {
ALOGV("Closing connection %p", connection->db);
+ if (connection->tableQuery != nullptr) {
+ sqlite3_finalize(connection->tableQuery);
+ }
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
// This can happen if sub-objects aren't closed first. Make sure the caller knows.
@@ -419,6 +428,46 @@ static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jlong connectionPtr,
return sqlite3_stmt_readonly(statement) != 0;
}
+static jboolean nativeUpdatesTempOnly(JNIEnv* env, jclass,
+ jlong connectionPtr, jlong statementPtr) {
+ sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+ int result = SQLITE_OK;
+ if (connection->tableQuery == nullptr) {
+ static char const* sql =
+ "SELECT COUNT(*) FROM tables_used(?) WHERE schema != 'temp' AND wr != 0";
+ result = sqlite3_prepare_v2(connection->db, sql, -1, &connection->tableQuery, nullptr);
+ if (result != SQLITE_OK) {
+ ALOGE("failed to compile query table: %s",
+ sqlite3_errstr(sqlite3_extended_errcode(connection->db)));
+ return false;
+ }
+ }
+
+ // A temporary, to simplify the code.
+ sqlite3_stmt* query = connection->tableQuery;
+ sqlite3_reset(query);
+ sqlite3_clear_bindings(query);
+ result = sqlite3_bind_text(query, 1, sqlite3_sql(statement), -1, SQLITE_STATIC);
+ if (result != SQLITE_OK) {
+ ALOGE("tables bind pointer returns %s", sqlite3_errstr(result));
+ return false;
+ }
+ result = sqlite3_step(query);
+ if (result != SQLITE_ROW) {
+ ALOGE("tables query error: %d/%s", result, sqlite3_errstr(result));
+ // Make sure the query is no longer bound to the statement.
+ sqlite3_clear_bindings(query);
+ return false;
+ }
+
+ int count = sqlite3_column_int(query, 0);
+ // Make sure the query is no longer bound to the statement SQL string.
+ sqlite3_clear_bindings(query);
+ return count == 0;
+}
+
static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
@@ -915,6 +964,8 @@ static const JNINativeMethod sMethods[] =
(void*)nativeGetParameterCount },
{ "nativeIsReadOnly", "(JJ)Z",
(void*)nativeIsReadOnly },
+ { "nativeUpdatesTempOnly", "(JJ)Z",
+ (void*)nativeUpdatesTempOnly },
{ "nativeGetColumnCount", "(JJ)I",
(void*)nativeGetColumnCount },
{ "nativeGetColumnName", "(JJI)Ljava/lang/String;",
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1baea2aecc3c..b6517117ca62 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -46,6 +46,7 @@ using vintf::toXml;
using vintf::Version;
using vintf::VintfObject;
using vintf::Vndk;
+using vintf::CheckFlags::ENABLE_ALL_CHECKS;
template<typename V>
static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
@@ -93,12 +94,13 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass)
return toJavaStringArray(env, cStrings);
}
-static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
std::string error;
- int32_t status = VintfObject::GetInstance()->checkCompatibility(&error,
- ::android::vintf::CheckFlags::DISABLE_AVB_CHECK);
+ int32_t status =
+ VintfObject::GetInstance()
+ ->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
if (status)
- LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error;
+ LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
return status;
}
@@ -170,7 +172,7 @@ static jobject android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersi
static const JNINativeMethod gVintfObjectMethods[] = {
{"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
- {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+ {"verifyBuildAtBoot", "()I", (void*)android_os_VintfObject_verifyBuildAtBoot},
{"getHalNamesAndVersions", "()[Ljava/lang/String;",
(void*)android_os_VintfObject_getHalNamesAndVersions},
{"getSepolicyVersion", "()Ljava/lang/String;",
diff --git a/core/jni/hwbinder/EphemeralStorage.cpp b/core/jni/hwbinder/EphemeralStorage.cpp
index 95bb42ea57c6..ef0750c815d7 100644
--- a/core/jni/hwbinder/EphemeralStorage.cpp
+++ b/core/jni/hwbinder/EphemeralStorage.cpp
@@ -164,7 +164,7 @@ void EphemeralStorage::release(JNIEnv *env) {
}
default:
- CHECK(!"Should not be here");
+ CHECK(!"Should not be here") << "Item type: " << item.mType;
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1eeffb9e2875..ef6caefd3daf 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7147,6 +7147,16 @@
android:label="@string/permlab_foregroundServiceFileManagement"
android:protectionLevel="normal|instant" />
+ <!-- @FlaggedApi("android.content.pm.introduce_media_processing_type")
+ Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProcessing".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING"
+ android:description="@string/permdesc_foregroundServiceMediaProcessing"
+ android:label="@string/permlab_foregroundServiceMediaProcessing"
+ android:protectionLevel="normal|instant" />
+
<!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
<p>Protection level: normal|appop|instant
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d1143c43ad61..8fae6db4114a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,9 +301,7 @@
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
<!-- Additional flag from base permission type: this permission will be granted to the
- retail demo app, as defined by the OEM.
- This flag has been replaced by the retail demo role and is a no-op since Android V.
- -->
+ retail demo app, as defined by the OEM. -->
<flag name="retailDemo" value="0x1000000" />
<!-- Additional flag from base permission type: this permission will be granted to the
recents app. -->
@@ -1746,6 +1744,12 @@
TODO: b/258855262 mark this field as {@code hide} once this bug is fixed.
<flag name="fileManagement" value="0x1000" />
-->
+ <!-- Media processing use cases such as video or photo editing and processing.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROCESSING} in order to use
+ this type.
+ -->
+ <flag name="mediaProcessing" value="0x2000" />
<!-- Use cases that can't be categorized into any other foreground service types, but also
can't use @link android.app.job.JobInfo.Builder} APIs.
See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index eddd81e78692..53a6270ed0e4 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -568,10 +568,6 @@
<color name="side_fps_button_color">#00677E</color>
<!-- Color for system bars -->
- <color name="status_bar_compatible">@android:color/black</color>
- <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
- color is set by the framework or not. -->
- <color name="status_bar_default">#00808080</color>
<color name="navigation_bar_compatible">@android:color/black</color>
<!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
color is set by the framework or not. -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 8d80af41680a..53464547c272 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -212,6 +212,36 @@
<bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool>
<java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" />
+ <!-- List of country codes where oem-enabled satellite services are either allowed or disallowed
+ by the device. Each country code is a lowercase 2 character ISO-3166-1 alpha-2.
+ -->
+ <string-array name="config_oem_enabled_satellite_country_codes">
+ </string-array>
+ <java-symbol type="array" name="config_oem_enabled_satellite_country_codes" />
+
+ <!-- The file storing S2-cell-based satellite access restriction of the countries defined by
+ config_oem_enabled_satellite_countries. -->
+ <string name="config_oem_enabled_satellite_s2cell_file"></string>
+ <java-symbol type="string" name="config_oem_enabled_satellite_s2cell_file" />
+
+ <!-- Whether to treat the countries defined by the config_oem_enabled_satellite_countries
+ as satellite-allowed areas. The default true value means the countries defined by
+ config_oem_enabled_satellite_countries will be treated as satellite-allowed areas.
+ -->
+ <bool name="config_oem_enabled_satellite_access_allow">true</bool>
+ <java-symbol type="bool" name="config_oem_enabled_satellite_access_allow" />
+
+ <!-- The time duration in seconds which is used to decide whether the Location returned from
+ LocationManager#getLastKnownLocation is fresh.
+
+ The Location is considered fresh if the duration from the Location's elapsed real time to
+ the current elapsed real time is less than this config. If the Location is considered
+ fresh, it will be used as the current location by Telephony to decide whether satellite
+ services should be allowed.
+ -->
+ <integer name="config_oem_enabled_satellite_location_fresh_duration">600</integer>
+ <java-symbol type="integer" name="config_oem_enabled_satellite_location_fresh_duration" />
+
<!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
will not perform handover if the target transport is out of service, or VoPS not
supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 540967d28d9b..17bb86a7c423 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -128,7 +128,7 @@
</staging-public-group>
<staging-public-group type="string" first-id="0x01ba0000">
- <!-- @hide @SystemApi -->
+ <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
<public name="config_defaultRetailDemo" />
</staging-public-group>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d2fb9e12d069..542e9d6f936f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1248,6 +1248,11 @@
<string name="permdesc_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_foregroundServiceMediaProcessing">run foreground service with the type \"mediaProcessing\"</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_foregroundServiceMediaProcessing">Allows the app to make use of foreground services with the type \"mediaProcessing\"</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b0a4c16b0551..d12ef2b95f06 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3095,8 +3095,6 @@
<java-symbol type="bool" name="config_navBarDefaultTransparent" />
<java-symbol type="color" name="navigation_bar_default"/>
<java-symbol type="color" name="navigation_bar_compatible"/>
- <java-symbol type="color" name="status_bar_default"/>
- <java-symbol type="color" name="status_bar_compatible"/>
<!-- EditText suggestion popup. -->
<java-symbol type="id" name="suggestionWindowContainer" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d5d67abaccb3..bdbf96b97c1e 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -190,7 +190,7 @@ please see themes_device_defaults.xml.
<item name="windowTranslucentStatus">false</item>
<item name="windowTranslucentNavigation">false</item>
<item name="windowDrawsSystemBarBackgrounds">false</item>
- <item name="statusBarColor">@color/status_bar_default</item>
+ <item name="statusBarColor">@color/black</item>
<item name="navigationBarColor">@color/navigation_bar_default</item>
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item>
<item name="windowContentTransitions">false</item>
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 1925588e8904..9d85b65d4dac 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -16,6 +16,8 @@
package android.app;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
@@ -26,6 +28,8 @@ import android.net.Uri;
import android.os.Parcel;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenPolicy;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -226,4 +230,66 @@ public class AutomaticZenRuleTest {
assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testCanUpdate_nullPolicyAndDeviceEffects() {
+ AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
+ Uri.parse("uri://short"));
+
+ AutomaticZenRule rule = builder.setUserModifiedFields(0)
+ .setZenPolicy(null)
+ .setDeviceEffects(null)
+ .build();
+
+ assertThat(rule.canUpdate()).isTrue();
+
+ rule = builder.setUserModifiedFields(1).build();
+ assertThat(rule.canUpdate()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testCanUpdate_policyModified() {
+ ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
+ ZenPolicy policy = policyBuilder.build();
+
+ AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
+ Uri.parse("uri://short"));
+ AutomaticZenRule rule = builder.setUserModifiedFields(0)
+ .setZenPolicy(policy)
+ .setDeviceEffects(null).build();
+
+ // Newly created ZenPolicy is not user modified.
+ assertThat(policy.getUserModifiedFields()).isEqualTo(0);
+ assertThat(rule.canUpdate()).isTrue();
+
+ policy = policyBuilder.setUserModifiedFields(1).build();
+ assertThat(policy.getUserModifiedFields()).isEqualTo(1);
+ rule = builder.setZenPolicy(policy).build();
+ assertThat(rule.canUpdate()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testCanUpdate_deviceEffectsModified() {
+ ZenDeviceEffects.Builder deviceEffectsBuilder =
+ new ZenDeviceEffects.Builder().setUserModifiedFields(0);
+ ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
+
+ AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
+ Uri.parse("uri://short"));
+ AutomaticZenRule rule = builder.setUserModifiedFields(0)
+ .setZenPolicy(null)
+ .setDeviceEffects(deviceEffects).build();
+
+ // Newly created ZenDeviceEffects is not user modified.
+ assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0);
+ assertThat(rule.canUpdate()).isTrue();
+
+ deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
+ assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
+ rule = builder.setDeviceEffects(deviceEffects).build();
+ assertThat(rule.canUpdate()).isFalse();
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 723c0812468c..a796a0fbb7ab 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -171,7 +171,8 @@ public class ObjectPoolTests {
@Test
public void testRecycleStartActivityItem() {
- testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic()));
+ testRecycle(() -> StartActivityItem.obtain(mActivityToken,
+ new ActivityOptions.SceneTransitionInfo()));
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index c0e2a4993e1c..3823033d48a1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -255,9 +255,9 @@ class TestUtils {
return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
- mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
- null /* activityClientController */, mShareableActivityToken,
- mLaunchedFromBubble, mTaskFragmentToken);
+ mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
+ mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */,
+ mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 07921bfc34f5..952cdd986614 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -262,7 +262,7 @@ public class TransactionParcelTests {
public void testStart() {
// Write to parcel
StartActivityItem item = StartActivityItem.obtain(mActivityToken,
- ActivityOptions.makeBasic());
+ new ActivityOptions.SceneTransitionInfo());
writeAndPrepareForReading(item);
// Read from parcel and assert
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 3fc08ee6fd2e..bd5f809dc689 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -26,6 +26,9 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -35,6 +38,7 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,6 +57,10 @@ import java.util.concurrent.TimeUnit;
@SmallTest
public class SQLiteDatabaseTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "SQLiteDatabaseTest";
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
@@ -347,4 +355,50 @@ public class SQLiteDatabaseTest {
assertTrue("ReadThread failed with errors: " + errors, errors.isEmpty());
}
+
+ @RequiresFlagsEnabled(Flags.FLAG_SQLITE_ALLOW_TEMP_TABLES)
+ @Test
+ public void testTempTable() {
+ boolean allowed;
+ allowed = true;
+ mDatabase.beginTransactionReadOnly();
+ try {
+ mDatabase.execSQL("CREATE TEMP TABLE t1 (i int, j int);");
+ mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (2, 20)");
+ mDatabase.execSQL("INSERT INTO t1 (i, j) VALUES (3, 30)");
+
+ final String sql = "SELECT i FROM t1 WHERE j = 30";
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+ assertTrue(s.step());
+ assertEquals(3, s.getColumnInt(0));
+ }
+
+ } catch (SQLiteException e) {
+ allowed = false;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ assertTrue(allowed);
+
+ // Repeat the test on the main schema.
+ allowed = true;
+ mDatabase.beginTransactionReadOnly();
+ try {
+ mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
+ mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (2, 20)");
+ mDatabase.execSQL("INSERT INTO t2 (i, j) VALUES (3, 30)");
+
+ final String sql = "SELECT i FROM t2 WHERE j = 30";
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(sql)) {
+ assertTrue(s.step());
+ assertEquals(3, s.getColumnInt(0));
+ }
+
+ } catch (SQLiteException e) {
+ allowed = false;
+ } finally {
+ mDatabase.endTransaction();
+ }
+ assertFalse(allowed);
+ }
}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 43683ffad432..ce2543a47cf5 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -56,6 +56,7 @@
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+ <permission name="android.permission.SATELLITE_COMMUNICATION"/>
<permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
<permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b9efe65d2754..a1ea2b8ce032 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -134,6 +134,7 @@ applications that come with the platform
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.LOCATION_BYPASS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" />
@@ -149,6 +150,7 @@ applications that come with the platform
<permission name="android.permission.REGISTER_CALL_PROVIDER"/>
<permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
+ <permission name="android.permission.SATELLITE_COMMUNICATION"/>
<permission name="android.permission.SEND_RESPOND_VIA_MESSAGE"/>
<permission name="android.permission.SHUTDOWN"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
new file mode 100644
index 000000000000..f15c1535a667
--- /dev/null
+++ b/data/keyboards/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2010 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.
+
+genrule {
+ name: "validate_framework_keymaps",
+ srcs: [
+ "*.kl",
+ "*.kcm",
+ "*.idc",
+ ],
+ tools: ["validatekeymaps"],
+ out: ["stamp"],
+ cmd: "$(location validatekeymaps) -q $(in) " +
+ "&& touch $(out)",
+ dist: {
+ targets: ["droidcore"],
+ },
+}
diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk
deleted file mode 100644
index 6ae88007f0f5..000000000000
--- a/data/keyboards/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 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.
-
-# This makefile performs build time validation of framework keymap files.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(LOCAL_PATH)/common.mk
-
-# Validate all key maps.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := validate_framework_keymaps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-intermediates := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE),,COMMON)
-LOCAL_BUILT_MODULE := $(intermediates)/stamp
-
-validatekeymaps := $(HOST_OUT_EXECUTABLES)/validatekeymaps$(HOST_EXECUTABLE_SUFFIX)
-$(LOCAL_BUILT_MODULE): PRIVATE_VALIDATEKEYMAPS := $(validatekeymaps)
-$(LOCAL_BUILT_MODULE) : $(framework_keylayouts) $(framework_keycharmaps) $(framework_keyconfigs) | $(validatekeymaps)
- $(hide) $(PRIVATE_VALIDATEKEYMAPS) -q $^
- $(hide) mkdir -p $(dir $@) && touch $@
-
-# Run validatekeymaps uncondionally for platform build.
-droidcore : $(LOCAL_BUILT_MODULE)
-
-# Reset temp vars.
-validatekeymaps :=
-framework_keylayouts :=
-framework_keycharmaps :=
-framework_keyconfigs :=
diff --git a/data/keyboards/Vendor_0957_Product_0031.kl b/data/keyboards/Vendor_0957_Product_0031.kl
new file mode 100644
index 000000000000..b47ee58c0c10
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0031.kl
@@ -0,0 +1,82 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Key Layout file for Google Reference RCU Remote with customizable button.
+#
+
+key 116 TV_POWER WAKE
+key 217 ASSIST WAKE
+key 423 MACRO_1 WAKE
+
+key 103 DPAD_UP
+key 108 DPAD_DOWN
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 353 DPAD_CENTER
+
+key 158 BACK
+key 172 HOME WAKE
+
+key 113 VOLUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+
+# custom keys
+key usage 0x000c01BB TV_INPUT
+
+key usage 0x000c0185 TV_TELETEXT
+key usage 0x000c0061 CAPTIONS
+
+key usage 0x000c01BD INFO
+key usage 0x000c0037 PERIOD
+
+key usage 0x000c0069 PROG_RED
+key usage 0x000c006A PROG_GREEN
+key usage 0x000c006C PROG_YELLOW
+key usage 0x000c006B PROG_BLUE
+key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
+key usage 0x000c00CD MEDIA_PLAY_PAUSE
+key usage 0x000c00B2 MEDIA_RECORD
+key usage 0x000c00B3 MEDIA_SKIP_FORWARD
+
+key usage 0x000c022A BOOKMARK
+key usage 0x000c01A2 ALL_APPS
+key usage 0x000c019C PROFILE_SWITCH
+
+key usage 0x000c0096 SETTINGS
+key usage 0x000c009F NOTIFICATION
+
+key usage 0x000c008D GUIDE
+key usage 0x000c0089 TV
+
+key usage 0x000c0187 FEATURED_APP_1 WAKE #FreeTv
+
+key usage 0x000c009C CHANNEL_UP
+key usage 0x000c009D CHANNEL_DOWN
+
+key usage 0x000c0077 BUTTON_3 WAKE #YouTube
+key usage 0x000c0078 BUTTON_4 WAKE #Netflix
+key usage 0x000c0079 BUTTON_6 WAKE
+key usage 0x000c007A BUTTON_7 WAKE \ No newline at end of file
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
deleted file mode 100644
index d75b691bd585..000000000000
--- a/data/keyboards/common.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2010 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.
-
-# This is the list of framework provided keylayouts and key character maps to include.
-# Used by Android.mk and keyboards.mk.
-
-framework_keylayouts := $(wildcard $(LOCAL_PATH)/*.kl)
-
-framework_keycharmaps := $(wildcard $(LOCAL_PATH)/*.kcm)
-
-framework_keyconfigs := $(wildcard $(LOCAL_PATH)/*.idc)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 592f9a57884c..80afb16d5832 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -382,9 +382,13 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
if (splitAttributes == null) {
return TaskFragmentAnimationParams.DEFAULT;
}
- return new TaskFragmentAnimationParams.Builder()
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
- .build();
+ final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+ if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(colorBackground.getColor())
+ .build();
+ } else {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6f356fa35d41..8b7fd108f031 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -893,8 +893,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new SplitAttributes.Builder()
.setSplitType(splitTypeToUpdate)
.setLayoutDirection(splitAttributes.getLayoutDirection())
- // TODO(b/263047900): Update extensions API.
- // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .setAnimationBackground(splitAttributes.getAnimationBackground())
.build();
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 60beb0b7f0a4..f471af052bf2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -25,6 +25,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.AnimationBackground;
import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
@@ -70,7 +71,7 @@ public class WindowExtensionsTest {
.isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
assertThat(splitAttributes.getSplitType())
.isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
- // TODO(b/263047900): Update extensions API.
- // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ assertThat(splitAttributes.getAnimationBackground())
+ .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index ac75c73d7e6d..06210ff98642 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
@@ -330,6 +331,9 @@ class ActivityEmbeddingAnimationRunner {
if (!animation.hasExtension()) {
continue;
}
+ if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)) {
+ continue;
+ }
final TransitionInfo.Change change = adapter.mChange;
if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
// Need to screenshot after startTransaction is applied otherwise activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index a49823648d01..81d963877e4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -403,8 +403,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mCurrentTracker.updateStartLocation();
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
- if (!shouldDispatchToAnimator()) {
- tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+ if (!shouldDispatchToAnimator() && mActiveCallback != null) {
+ tryDispatchAppOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
@@ -507,7 +507,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
// App is handling back animation. Cancel system animation latency tracking.
cancelLatencyTracking();
- tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+ tryDispatchAppOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
}
}
@@ -551,14 +551,24 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mBackNavigationInfo.isPrepareRemoteAnimation();
}
- private void tryDispatchOnBackStarted(IOnBackInvokedCallback callback,
+ private void tryDispatchAppOnBackStarted(
+ IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (callback == null || mOnBackStartDispatched) {
+ if (mOnBackStartDispatched && callback != null) {
+ return;
+ }
+ dispatchOnBackStarted(callback, backEvent);
+ mOnBackStartDispatched = true;
+ }
+
+ private void dispatchOnBackStarted(
+ IOnBackInvokedCallback callback,
+ BackMotionEvent backEvent) {
+ if (callback == null) {
return;
}
try {
callback.onBackStarted(backEvent);
- mOnBackStartDispatched = true;
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
@@ -940,9 +950,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (apps.length >= 1) {
mCurrentTracker.updateStartLocation();
- tryDispatchOnBackStarted(
- mActiveCallback,
- mCurrentTracker.createStartEvent(apps[0]));
+ BackMotionEvent startEvent =
+ mCurrentTracker.createStartEvent(apps[0]);
+ // {@code mActiveCallback} is the callback from
+ // the BackAnimationRunners and not a real app-side
+ // callback. We also dispatch to the app-side callback
+ // (which should be a system callback with PRIORITY_SYSTEM)
+ // to keep consistent with app registered callbacks.
+ dispatchOnBackStarted(mActiveCallback, startEvent);
+ tryDispatchAppOnBackStarted(
+ mBackNavigationInfo.getOnBackInvokedCallback(),
+ startEvent);
}
// Dispatch the first progress after animation start for
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0693543515b4..662f325be38c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,7 +16,7 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +24,27 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -135,4 +143,28 @@ public class SplitScreenUtils {
return isLandscape;
}
}
+
+ /** Returns the component from a PendingIntent */
+ @Nullable
+ public static ComponentName getComponent(@Nullable PendingIntent pendingIntent) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) {
+ return null;
+ }
+ return pendingIntent.getIntent().getComponent();
+ }
+
+ /** Returns the component from a shortcut */
+ @Nullable
+ public static ComponentName getShortcutComponent(@NonNull String packageName, String shortcutId,
+ @NonNull UserHandle user, @NonNull LauncherApps launcherApps) {
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+ query.setPackage(packageName);
+ query.setShortcutIds(Arrays.asList(shortcutId));
+ query.setQueryFlags(FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED);
+ List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user);
+ ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
+ ? shortcuts.get(0)
+ : null;
+ return info != null ? info.getActivity() : null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 0ef047f44909..36f06e8bdb3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -210,7 +210,6 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
@@ -226,7 +225,6 @@ public abstract class WMShellModule {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
rootTaskDisplayAreaOrganizer);
}
return new CaptionWindowDecorViewModel(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4a1bcaa7168a..b1c43c19366d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -320,9 +320,8 @@ class DesktopTasksController(
}
/** Move a task with given `taskId` to fullscreen */
- fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+ fun moveToFullscreen(taskId: Int) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(task, task.positionInParent)
}
}
@@ -906,20 +905,17 @@ class DesktopTasksController(
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
* @param taskBounds the updated bounds of the task being dragged.
- * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect,
- windowDecor: DesktopModeWindowDecoration
+ taskBounds: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
if (taskBounds.top <= transitionAreaHeight) {
- windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
if (inputCoordinate.x <= transitionAreaWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9debb25ff296..0218493589b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -54,8 +54,6 @@ class ToggleResizeDesktopTaskTransitionHandler(
taskId: Int,
windowDecoration: DesktopModeWindowDecoration
) {
- // Pause relayout until the transition animation finishes.
- windowDecoration.incrementRelayoutBlock()
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
taskToDecorationMap.put(taskId, windowDecoration)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6b6a7bc42046..ffcc526eac40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,7 +112,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
onChangeTransitionReady(change, startT, finishT);
break;
}
- mWindowDecorViewModel.onTransitionReady(transition, info, change);
}
mTransitionToTaskInfo.put(transition, taskInfoList);
}
@@ -153,8 +152,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- mWindowDecorViewModel.onTransitionMerged(merged, playing);
-
final List<ActivityManager.RunningTaskInfo> infoOfMerged =
mTransitionToTaskInfo.get(merged);
if (infoOfMerged == null) {
@@ -178,7 +175,6 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
final List<ActivityManager.RunningTaskInfo> taskInfo =
mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
mTransitionToTaskInfo.remove(transition);
- mWindowDecorViewModel.onTransitionFinished(transition);
for (int i = 0; i < taskInfo.size(); ++i) {
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 3906599b7581..8b3de6298b2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -52,9 +52,10 @@ interface IPip {
* @param componentName ComponentName represents the Activity
* @param destinationBounds the destination bounds the PiP window lands into
* @param overlay an optional overlay to fade out after entering PiP
+ * @param appBounds the bounds used to set the buffer size of the optional content overlay
*/
oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
- in Rect destinationBounds, in SurfaceControl overlay) = 2;
+ in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
/**
* Notifies the swiping Activity to PiP onto home transition is aborted
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 3635165d76ce..a9a3f788cb7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -334,6 +334,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Nullable
SurfaceControl mPipOverlay;
+ /**
+ * The app bounds used for the buffer size of the
+ * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+ *
+ * Note that this is empty if the overlay is removed or if it's some other type of overlay
+ * defined in {@link PipContentOverlay}.
+ */
+ @NonNull
+ final Rect mAppBounds = new Rect();
+
public PipTaskOrganizer(Context context,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@@ -464,15 +474,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ SurfaceControl overlay, Rect appBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
+ "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
// do nothing if there is no startSwipePipToHome being called before
if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
return;
}
mPipBoundsState.setBounds(destinationBounds);
- mPipOverlay = overlay;
+ setContentOverlay(overlay, appBounds);
if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
// With Shell transition, the overlay was attached to the remote transition leash, which
// will be removed when the current transition is finished, so we need to reparent it
@@ -1888,7 +1898,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
"%s: trying to remove overlay (%s) which is not local reference (%s)",
TAG, surface, mPipOverlay);
}
- mPipOverlay = null;
+ clearContentOverlay();
}
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// Avoid double removal, which is fatal.
@@ -1905,6 +1915,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (callback != null) callback.run();
}
+ void clearContentOverlay() {
+ mPipOverlay = null;
+ mAppBounds.setEmpty();
+ }
+
+ void setContentOverlay(@Nullable SurfaceControl leash, @NonNull Rect appBounds) {
+ mPipOverlay = leash;
+ if (mPipOverlay != null) {
+ mAppBounds.set(appBounds);
+ } else {
+ mAppBounds.setEmpty();
+ }
+ }
+
private void resetShadowRadius() {
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
// mLeash is undefined when in PipTransitionState.UNDEFINED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index f5f15d81ea44..89dcc4c1d261 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -141,8 +141,6 @@ public class PipTransition extends PipTransitionController {
/** Whether the PIP window has fade out for fixed rotation. */
private boolean mHasFadeOut;
- private Rect mInitBounds = new Rect();
-
/** Used for setting transform to a transaction from animator. */
private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
new PipAnimationController.PipTransactionHandler() {
@@ -465,12 +463,13 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
.resetScale(tx, leash, destinationBounds)
.round(tx, leash, true /* applyCornerRadius */);
- if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+ final Rect appBounds = mPipOrganizer.mAppBounds;
+ if (mPipOrganizer.mPipOverlay != null && !appBounds.isEmpty()) {
// Resetting the scale for pinned task while re-adjusting its crop,
// also scales the overlay. So we need to update the overlay leash too.
Rect overlayBounds = new Rect(destinationBounds);
final int overlaySize = PipContentOverlay.PipAppIconOverlay
- .getOverlaySize(mInitBounds, destinationBounds);
+ .getOverlaySize(appBounds, destinationBounds);
overlayBounds.offsetTo(
(destinationBounds.width() - overlaySize) / 2,
@@ -479,7 +478,6 @@ public class PipTransition extends PipTransitionController {
mPipOrganizer.mPipOverlay, overlayBounds);
}
}
- mInitBounds.setEmpty();
wct.setBoundsChangeTransaction(taskInfo.token, tx);
}
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -617,7 +615,7 @@ public class PipTransition extends PipTransitionController {
// if overlay is present remove it immediately, as exit transition came before it faded out
if (mPipOrganizer.mPipOverlay != null) {
startTransaction.remove(mPipOrganizer.mPipOverlay);
- clearPipOverlay();
+ mPipOrganizer.clearContentOverlay();
}
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -951,9 +949,6 @@ public class PipTransition extends PipTransitionController {
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
final Rect currentBounds = pipChange.getStartAbsBounds();
- // Cache the start bounds for overlay manipulations as a part of finishCallback.
- mInitBounds.set(currentBounds);
-
int rotationDelta = deltaRotation(startRotation, endRotation);
Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -1022,7 +1017,7 @@ public class PipTransition extends PipTransitionController {
} else {
throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
- mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
+ mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -1073,10 +1068,6 @@ public class PipTransition extends PipTransitionController {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
}
- Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
- if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
- mInitBounds.set(appBounds);
- }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -1106,7 +1097,7 @@ public class PipTransition extends PipTransitionController {
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
if (swipePipToHomeOverlay != null) {
mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
- this::clearPipOverlay /* callback */, false /* withStartDelay */);
+ null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
}
@@ -1250,10 +1241,6 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.updateMenuBounds(destinationBounds);
}
- private void clearPipOverlay() {
- mPipOrganizer.mPipOverlay = null;
- }
-
@Override
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63f20fd8e997..238e6b5bf2af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -982,8 +982,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
- mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+ SurfaceControl overlay, Rect appBounds) {
+ mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+ appBounds);
}
private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1280,13 +1281,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void stopSwipePipToHome(int taskId, ComponentName componentName,
- Rect destinationBounds, SurfaceControl overlay) {
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
if (overlay != null) {
overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
}
executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
(controller) -> controller.stopSwipePipToHome(
- taskId, componentName, destinationBounds, overlay));
+ taskId, componentName, destinationBounds, overlay, appBounds));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7b5709769369..880d95286de6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -23,12 +23,15 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getComponent;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getShortcutComponent;
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
@@ -47,6 +50,8 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -171,6 +176,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final LauncherApps mLauncherApps;
private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final ShellExecutor mMainExecutor;
private final SplitScreenImpl mImpl = new SplitScreenImpl();
@@ -186,7 +193,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
private final Optional<DesktopTasksController> mDesktopTasksController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
- private final String[] mAppsSupportMultiInstances;
+ // A static allow list of apps which support multi-instance
+ private final String[] mAppsSupportingMultiInstance;
@VisibleForTesting
StageCoordinator mStageCoordinator;
@@ -220,6 +228,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mPackageManager = context.getPackageManager();
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -242,7 +252,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// TODO(255224696): Remove the config once having a way for client apps to opt-in
// multi-instances split.
- mAppsSupportMultiInstances = mContext.getResources()
+ mAppsSupportingMultiInstance = mContext.getResources()
.getStringArray(R.array.config_appsSupportMultiInstancesSplit);
}
@@ -266,12 +276,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
WindowDecorViewModel windowDecorViewModel,
DesktopTasksController desktopTasksController,
ShellExecutor mainExecutor,
- StageCoordinator stageCoordinator) {
+ StageCoordinator stageCoordinator,
+ String[] appsSupportingMultiInstance) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
+ mPackageManager = context.getPackageManager();
+ mLauncherApps = context.getSystemService(LauncherApps.class);
mRootTDAOrganizer = rootTDAOrganizer;
mMainExecutor = mainExecutor;
mDisplayController = displayController;
@@ -288,8 +301,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
- mAppsSupportMultiInstances = mContext.getResources()
- .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
+ mAppsSupportingMultiInstance = appsSupportingMultiInstance;
}
public SplitScreen asSplitScreen() {
@@ -588,7 +600,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
- if (supportMultiInstancesSplit(packageName)) {
+ if (supportsMultiInstanceSplit(getShortcutComponent(packageName, shortcutId, user,
+ mLauncherApps))) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else if (isSplitScreenVisible()) {
@@ -609,7 +622,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
activityOptions.toBundle(), user);
}
- void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +634,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -640,7 +653,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
- void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
@@ -653,7 +666,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId1 = shortcutInfo.getUserId();
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -692,7 +705,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +735,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
@@ -757,7 +770,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
@@ -794,7 +807,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
setSecondIntentMultipleTask = true;
@@ -856,7 +869,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return;
}
if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (supportMultiInstancesSplit(packageName1)) {
+ if (supportsMultiInstanceSplit(getComponent(intent))) {
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
// the split and there is no reusable background task.
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,16 +928,63 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return taskInfo != null ? taskInfo.userId : -1;
}
+ /**
+ * Returns whether a specific component desires to be launched in multiple instances for
+ * split screen.
+ */
@VisibleForTesting
- boolean supportMultiInstancesSplit(String packageName) {
- if (packageName != null) {
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
- }
+ boolean supportsMultiInstanceSplit(@Nullable ComponentName componentName) {
+ if (componentName == null || componentName.getPackageName() == null) {
+ // TODO(b/262864589): Handle empty component case
+ return false;
+ }
+
+ // Check the pre-defined allow list
+ final String packageName = componentName.getPackageName();
+ for (int i = 0; i < mAppsSupportingMultiInstance.length; i++) {
+ if (mAppsSupportingMultiInstance[i].equals(packageName)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "application=%s in allowlist supports multi-instance", packageName);
+ return true;
+ }
+ }
+
+ // Check the activity property first
+ try {
+ final PackageManager.Property activityProp = mPackageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName);
+ // If the above call doesn't throw a NameNotFoundException, then the activity property
+ // should override the application property value
+ if (activityProp.isBoolean()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "activity=%s supports multi-instance", componentName);
+ return activityProp.getBoolean();
+ } else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Warning: property=%s for activity=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName,
+ activityProp.getType());
}
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ // Not specified in the activity, fall through
}
+ // Check the application property otherwise
+ try {
+ final PackageManager.Property appProp = mPackageManager.getProperty(
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName);
+ if (appProp.isBoolean()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "application=%s supports multi-instance", packageName);
+ return appProp.getBoolean();
+ } else {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Warning: property=%s for application=%s has non-bool type=%d",
+ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.getType());
+ }
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ // Not specified in either application or activity
+ }
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f58aeac918b5..a666e208a1b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -73,6 +73,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.Quantizer;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.policy.PhoneWindow;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -245,16 +246,19 @@ public class SplashscreenContentDrawer {
} else {
windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
}
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */)
+ ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ : params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
final int displayId = taskInfo.displayId;
// Assumes it's safe to show starting windows of launched apps while
// the keyguard is being hidden. This is okay because starting windows never show
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 9f20f49b4094..db845137540b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,9 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -84,7 +84,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private UnfoldTransitionHandler mUnfoldHandler;
private ActivityEmbeddingController mActivityEmbeddingController;
- private class MixedTransition {
+ private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -175,7 +175,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
joinFinishArgs(wct);
if (mInFlightSubAnimations == 0) {
- mActiveTransitions.remove(MixedTransition.this);
mFinishCB.onTransitionFinished(mFinishWCT);
}
}
@@ -401,8 +400,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
final MixedTransition keyguardMixed =
new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
mActiveTransitions.add(keyguardMixed);
- final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
- startTransaction, finishTransaction, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(keyguardMixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ final boolean hasAnimateKeyguard = animateKeyguard(
+ keyguardMixed, info, startTransaction, finishTransaction, callback);
if (hasAnimateKeyguard) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Converting mixed transition into a keyguard transition");
@@ -420,27 +423,34 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mixed == null) return false;
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
- return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
- return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
- finishTransaction, finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ final MixedTransition chosenTransition = mixed;
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(chosenTransition);
+ finishCallback.onTransitionFinished(wct);
+ };
+
+ if (chosenTransition.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ return animateEnterPipFromSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType
+ == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+ return animateEnterPipFromActivityEmbedding(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
- } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
- startTransaction, finishTransaction, finishCallback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
// Consume the transition on remote handler if the leftover handler already handle this
// transition. And if it cannot, the transition will be handled by remote handler, so
// don't consume here.
// Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
- if (handledToPip && mixed.mHasRequestToRemote
- && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ if (handledToPip && chosenTransition.mHasRequestToRemote
+ && chosenTransition.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
}
return handledToPip;
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
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
@@ -449,28 +459,29 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
if (mPipHandler.isEnteringPip(change, info.getType())
&& mSplitHandler.getSplitItemPosition(change.getLastParent())
!= SPLIT_POSITION_UNDEFINED) {
- return animateEnterPipFromSplit(mixed, info, startTransaction,
- finishTransaction, finishCallback);
+ return animateEnterPipFromSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
}
}
- return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
- return animateKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
- return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
- return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
- finishCallback);
- } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
- return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
+ return animateRecentsDuringSplit(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_KEYGUARD) {
+ return animateKeyguard(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
+ return animateRecentsDuringKeyguard(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ return animateRecentsDuringDesktop(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
+ } else if (chosenTransition.mType == MixedTransition.TYPE_UNFOLD) {
+ return animateUnfold(
+ chosenTransition, info, startTransaction, finishTransaction, callback);
} else {
- mActiveTransitions.remove(mixed);
+ mActiveTransitions.remove(chosenTransition);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
- + mixed.mType);
+ + chosenTransition.mType);
}
}
@@ -727,7 +738,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
mActiveTransitions.add(mixed);
- return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
+ Transitions.TransitionFinishCallback callback = wct -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct);
+ };
+ return animateEnterPipFromSplit(mixed, info, startT, finishT, callback);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cebc4006656a..1a793a16f254 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,13 +23,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -80,16 +78,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
- public void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- @Override
- public void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- @Override
- public void onTransitionFinished(IBinder transition) {}
-
- @Override
public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 61a8e9b5dd59..d07c64670bd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -43,7 +42,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -59,8 +57,6 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -80,8 +76,6 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,7 +109,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private final Optional<DesktopTasksController> mDesktopTasksController;
- private final RecentsTransitionHandler mRecentsTransitionHandler;
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -154,7 +147,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
this(
@@ -170,7 +162,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
transitions,
desktopTasksController,
- recentsTransitionHandler,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -191,7 +182,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
- RecentsTransitionHandler recentsTransitionHandler,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -207,7 +197,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController;
- mRecentsTransitionHandler = recentsTransitionHandler;
mShellCommandHandler = shellCommandHandler;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
mInputMonitorFactory = inputMonitorFactory;
@@ -219,12 +208,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
- mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
- @Override
- public void onTransitionStarted(IBinder transition) {
- blockRelayoutOnTransitionStarted(transition);
- }
- });
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
@@ -264,48 +247,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
- public void onTransitionReady(
- @NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull TransitionInfo.Change change) {
- if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
- || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
- .addTransitionPausingRelayout(transition);
- } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
- && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
- && change.getTaskInfo() != null) {
- final DesktopModeWindowDecoration decor =
- mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
- if (decor != null) {
- decor.addTransitionPausingRelayout(transition);
- }
- } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
- && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
- && change.getTaskInfo() != null) {
- blockRelayoutOnTransitionStarted(transition);
- }
- }
-
- @Override
- public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.mergeTransitionPausingRelayout(merged, playing);
- }
- }
-
- @Override
- public void onTransitionFinished(@NonNull IBinder transition) {
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.removeTransitionPausingRelayout(transition);
- }
- }
-
- @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
@@ -365,16 +306,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
- private void blockRelayoutOnTransitionStarted(IBinder transition) {
- // Block relayout on window decorations originating from #onTaskInfoChanges until the
- // animation completes to avoid interfering with the transition animation.
- for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
- final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
- decor.incrementRelayoutBlock();
- decor.addTransitionPausingRelayout(transition);
- }
- }
-
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
DragDetector.MotionEventHandler {
@@ -425,7 +356,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- decoration.incrementRelayoutBlock();
mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
closeOtherSplitTask(mTaskId);
}
@@ -436,7 +366,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mSplitScreenController.moveTaskToFullscreen(mTaskId);
} else {
mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+ c.moveToFullscreen(mTaskId));
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
@@ -604,7 +534,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
+ newTaskBounds));
mIsDragging = false;
return true;
}
@@ -812,20 +742,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> {
- final int taskId = relevantDecor.mTaskInfo.taskId;
- relevantDecor.incrementRelayoutBlock();
- if (isTaskInSplitScreen(taskId)) {
- final DesktopModeWindowDecoration otherDecor =
- mWindowDecorByTaskId.get(
- getOtherSplitTask(taskId).taskId);
- if (otherDecor != null) {
- otherDecor.incrementRelayoutBlock();
- }
- }
- c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator, relevantDecor);
- });
+ c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator, relevantDecor));
}
}
if (mMoveToDesktopAnimator != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d08b655e43f8..5f77192fb623 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -36,7 +36,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.IBinder;
import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
@@ -62,8 +61,6 @@ import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowD
import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
-import java.util.HashSet;
-import java.util.Set;
import java.util.function.Supplier;
/**
@@ -104,8 +101,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private ExclusionRegionListener mExclusionRegionListener;
- private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
- private int mRelayoutBlock;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
DesktopModeWindowDecoration(
@@ -179,13 +174,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
- // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
- // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
- // and interferes with the transition animation that is playing at the same time.
- if (mRelayoutBlock > 0) {
- return;
- }
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
// The crop and position of the task should only be set when a task is fluid resizing. In
// all other cases, it is expected that the transition handler positions and crops the task
@@ -737,16 +725,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return exclusionRegion;
}
- /**
- * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
- * mRelayoutBlock
- */
- void removeTransitionPausingRelayout(IBinder transition) {
- if (mTransitionsPausingRelayout.remove(transition)) {
- mRelayoutBlock--;
- }
- }
-
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
return getCaptionHeightIdStatic(windowingMode);
@@ -767,35 +745,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return R.id.desktop_mode_caption;
}
- /**
- * Add transition to mTransitionsPausingRelayout
- */
- void addTransitionPausingRelayout(IBinder transition) {
- mTransitionsPausingRelayout.add(transition);
- }
-
- /**
- * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
- * remove the merged transition from the set and add the transition it was merged into.
- */
- public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
- if (mTransitionsPausingRelayout.remove(merged)) {
- mTransitionsPausingRelayout.add(playing);
- }
- }
-
- /**
- * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
- */
- public void incrementRelayoutBlock() {
- mRelayoutBlock++;
- }
-
@Override
public String toString() {
return "{"
+ "mPositionInParent=" + mPositionInParent + ", "
- + "mRelayoutBlock=" + mRelayoutBlock + ", "
+ "taskId=" + mTaskInfo.taskId + ", "
+ "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+ "isFocused=" + isFocused()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index ae1a3d914be3..01a6012ea314 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,9 +17,7 @@
package com.android.wm.shell.windowdecor;
import android.app.ActivityManager;
-import android.os.IBinder;
import android.view.SurfaceControl;
-import android.window.TransitionInfo;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,34 +101,4 @@ public interface WindowDecorViewModel {
* @param taskInfo the info of the task
*/
void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-
- /**
- * Notifies that a shell transition is about to start. If the transition is of type
- * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
- * task after the transition has ended.
- *
- * @param transition the ready transaction
- * @param info of Transition to check if relayout needs to be paused for a task
- * @param change a change in the given transition
- */
- default void onTransitionReady(IBinder transition, TransitionInfo info,
- TransitionInfo.Change change) {}
-
- /**
- * Notifies that a shell transition is about to merge with another to give the window
- * decoration a chance to prepare for this merge.
- *
- * @param merged the transaction being merged
- * @param playing the transaction being merged into
- */
- default void onTransitionMerged(IBinder merged, IBinder playing) {}
-
- /**
- * Notifies that a shell transition is about to finish to give the window decoration a chance
- * to clean up.
- *
- * @param transaction
- */
- default void onTransitionFinished(IBinder transaction) {}
-
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
new file mode 100644
index 000000000000..955660c396d0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.common.split
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.wm.shell.ShellTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenUtilsTests : ShellTestCase() {
+
+ @Test
+ fun getShortcutComponent_nullShortcuts() {
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(null)
+ }
+ assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_noShortcuts() {
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(ArrayList<ShortcutInfo>())
+ }
+ assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ @Test
+ fun getShortcutComponent_validShortcut() {
+ val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+ val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build()
+ val launcherApps = mock(LauncherApps::class.java).also {
+ `when`(it.getShortcuts(any(), any())).thenReturn(arrayListOf(shortcutInfo))
+ }
+ assertEquals(component, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+ TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+ }
+
+ companion object {
+ val TEST_PACKAGE = "com.android.wm.shell.common.split"
+ val TEST_ACTIVITY = "TestActivity";
+ val TEST_SHORTCUT_ID = "test_shortcut_1"
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 94c862bd7a4f..9249b0a0dfda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -393,7 +393,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -403,7 +403,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
@@ -411,7 +411,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
- controller.moveToFullscreen(999, desktopModeWindowDecoration)
+ controller.moveToFullscreen(999)
verifyWCTNotExecuted()
}
@@ -420,7 +420,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId)
with(getLatestExitDesktopWct()) {
assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 855b7ee04702..12a5594ae1da 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -36,6 +37,8 @@ import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -46,8 +49,10 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.test.annotation.UiThreadTest;
@@ -55,6 +60,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -91,6 +97,10 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
+ private static final String TEST_PACKAGE = "com.android.wm.shell.splitscreen";
+ private static final String TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.splitscreen.fake";
+ private static final String TEST_ACTIVITY = "TestActivity";
+
@Mock ShellInit mShellInit;
@Mock ShellCommandHandler mShellCommandHandler;
@Mock ShellTaskOrganizer mTaskOrganizer;
@@ -118,6 +128,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
public void setup() {
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
+ String[] appsSupportingMultiInstance = mContext.getResources()
+ .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
@@ -125,7 +137,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
- mDesktopTasksController, mMainExecutor, mStageCoordinator));
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ appsSupportingMultiInstance));
}
@Test
@@ -200,7 +213,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
- doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -237,12 +250,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@Test
public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
@@ -259,14 +273,14 @@ public class SplitScreenControllerTests extends ShellTestCase {
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null);
- verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+ verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
}
@Test
public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
- doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doReturn(false).when(mSplitScreenController).supportsMultiInstanceSplit(any());
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -283,6 +297,130 @@ public class SplitScreenControllerTests extends ShellTestCase {
verify(mStageCoordinator).switchSplitPosition(anyString());
}
+ @Test
+ public void supportsMultiInstanceSplit_inStaticAllowList() {
+ String[] allowList = { TEST_PACKAGE };
+ SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ allowList);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_notInStaticAllowList() {
+ String[] allowList = { TEST_PACKAGE };
+ SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ allowList);
+ ComponentName component = new ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY);
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_activityPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ PackageManager.Property activityProp = new PackageManager.Property("", true, "", "");
+ doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", false, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect activity property to override application property
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ PackageManager.Property activityProp = new PackageManager.Property("", false, "", "");
+ doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect activity property to override application property
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+ PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+ doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ // Expect fall through to app property
+ assertEquals(true, controller.supportsMultiInstanceSplit(component));
+ }
+
+ @Test
+ public void supportsMultiInstanceSplit_noActivityOrAppProperty()
+ throws PackageManager.NameNotFoundException {
+ Context context = spy(mContext);
+ ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+ PackageManager pm = mock(PackageManager.class);
+ doReturn(pm).when(context).getPackageManager();
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+ doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+ eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.getPackageName()));
+
+ SplitScreenController controller = new SplitScreenController(context, mShellInit,
+ mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+ mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+ mDesktopTasksController, mMainExecutor, mStageCoordinator,
+ new String[0]);
+ assertEquals(false, controller.supportsMultiInstanceSplit(component));
+ }
+
private Intent createStartIntent(String activityName) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 883c24e78076..f84685a92b57 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -27,7 +27,6 @@ import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
-import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
@@ -40,8 +39,6 @@ import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.window.TransitionInfo
import androidx.test.filters.SmallTest
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -53,8 +50,6 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController
-import com.android.wm.shell.recents.RecentsTransitionHandler
-import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -100,7 +95,6 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockShellController: ShellController
@Mock private lateinit var mockShellExecutor: ShellExecutor
@Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -127,7 +121,6 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
- mockRecentsTransitionHandler,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
@@ -275,48 +268,6 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
}
@Test
- fun testRelayoutBlockedDuringRecentsTransition() {
- val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
- verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
-
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // Now call back when a Recents transition starts.
- recentsCaptor.firstValue.onTransitionStarted(transition)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
-
- @Test
- fun testRelayoutBlockedDuringKeyguardTransition() {
- val transition = mock(IBinder::class.java)
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
- val decoration = setUpMockDecorationForTask(task)
- val transitionInfo = mock(TransitionInfo::class.java)
- val transitionChange = mock(TransitionInfo.Change::class.java)
- val taskInfo = mock(RunningTaskInfo()::class.java)
-
- // Replicate a keyguard going away transition for a task
- whenever(transitionInfo.getFlags())
- .thenReturn(WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
- whenever(transitionChange.getMode()).thenReturn(WindowManager.TRANSIT_TO_FRONT)
- whenever(transitionChange.getTaskInfo()).thenReturn(taskInfo)
-
- // Make sure a window decorations exists first by launching a freeform task.
- onTaskOpening(task)
- // OnTransition ready is called when a keyguard going away transition happens
- desktopModeWindowDecorViewModel
- .onTransitionReady(transition, transitionInfo, transitionChange)
-
- verify(decoration).incrementRelayoutBlock()
- verify(decoration).addTransitionPausingRelayout(transition)
- }
- @Test
fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
val decoration = setUpMockDecorationForTask(task)
diff --git a/location/api/current.txt b/location/api/current.txt
index 0c23d8cd77e0..c55676bc1e78 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -414,7 +414,7 @@ package android.location {
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
- field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
+ field public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 5e3f8033d116..7a667ae4ef6c 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -78,9 +78,7 @@ public final class GnssNavigationMessage implements Parcelable {
public static final int TYPE_GAL_F = 0x0602;
/**
* NavIC L5 C/A message contained in the structure.
- * @deprecated deprecated.
*/
- @Deprecated
public static final int TYPE_IRN_L5CA = 0x0701;
/** NavIC L5 message contained in the structure. */
@FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 0eabe66e9a69..838630fcccb9 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -943,6 +943,10 @@ public final class MediaRoute2Info implements Parcelable {
.append(getId())
.append(", name=")
.append(getName())
+ .append(", type=")
+ .append(getDeviceTypeString(getType()))
+ .append(", isSystem=")
+ .append(isSystemRoute())
.append(", features=")
.append(getFeatures())
.append(", iconUri=")
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 60497fe22dcf..47637b82111a 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,7 +15,10 @@
*/
package android.media.session;
+import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
+
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -189,7 +192,8 @@ public final class PlaybackState implements Parcelable {
*/
@IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
- STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
+ STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM,
+ STATE_PLAYBACK_SUPPRESSED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
@@ -286,6 +290,19 @@ public final class PlaybackState implements Parcelable {
public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
/**
+ * State indicating that playback is paused due to an external transient interruption, like a
+ * phone call.
+ *
+ * <p>This state is different from {@link #STATE_PAUSED} in that it is deemed transitory,
+ * possibly allowing the service associated to the session in this state to run in the
+ * foreground.
+ *
+ * @see Builder#setState
+ */
+ @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+ public static final int STATE_PLAYBACK_SUPPRESSED = 12;
+
+ /**
* Use this value for the position to indicate the position is not known.
*/
public static final long PLAYBACK_POSITION_UNKNOWN = -1;
@@ -384,6 +401,7 @@ public final class PlaybackState implements Parcelable {
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
@State
@@ -507,6 +525,7 @@ public final class PlaybackState implements Parcelable {
* <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
* <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
* <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li>{@link #STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*/
public boolean isActive() {
@@ -519,33 +538,12 @@ public final class PlaybackState implements Parcelable {
case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_CONNECTING:
case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_PLAYBACK_SUPPRESSED:
return true;
}
return false;
}
- /**
- * Returns whether the service holding the media session should run in the foreground when the
- * media session has this playback state or not.
- *
- * @hide
- */
- public boolean shouldAllowServiceToRunInForeground() {
- switch (mState) {
- case PlaybackState.STATE_PLAYING:
- case PlaybackState.STATE_FAST_FORWARDING:
- case PlaybackState.STATE_REWINDING:
- case PlaybackState.STATE_BUFFERING:
- case PlaybackState.STATE_CONNECTING:
- case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
- case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
- return true;
- default:
- return false;
- }
- }
-
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
@@ -586,6 +584,8 @@ public final class PlaybackState implements Parcelable {
return "SKIPPING_TO_NEXT";
case STATE_SKIPPING_TO_QUEUE_ITEM:
return "SKIPPING_TO_QUEUE_ITEM";
+ case STATE_PLAYBACK_SUPPRESSED:
+ return "STATE_PLAYBACK_SUPPRESSED";
default:
return "UNKNOWN";
}
@@ -823,6 +823,7 @@ public final class PlaybackState implements Parcelable {
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
@@ -867,6 +868,7 @@ public final class PlaybackState implements Parcelable {
* <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
* <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+ * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
* </ul>
*
* @param state The current state of playback.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 326e533df0d8..aeabbd53d177 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -46,6 +46,7 @@ import com.android.packageinstaller.common.InstallEventReceiver
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_NONE
import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
@@ -283,14 +284,15 @@ class InstallRepository(private val context: Context) {
createSessionParams(intent, pfd, uri.toString())
stagedSessionId = packageInstaller.createSession(params)
}
- } catch (e: IOException) {
+ } catch (e: Exception) {
Log.w(LOG_TAG, "Failed to create a staging session", e)
_stagingResult.value = InstallAborted(
ABORT_REASON_INTERNAL_ERROR,
resultIntent = Intent().putExtra(
Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
),
- activityResultCode = Activity.RESULT_FIRST_USER
+ activityResultCode = Activity.RESULT_FIRST_USER,
+ errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
)
return
}
@@ -313,6 +315,14 @@ class InstallRepository(private val context: Context) {
)
}
}
+ } else {
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_URI
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index be49b39e9a48..bbb9bca6db51 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -122,13 +122,14 @@ data class InstallAborted(
*/
val resultIntent: Intent? = null,
val activityResultCode: Int = Activity.RESULT_CANCELED,
- val errorDialogType: Int? = 0,
+ val errorDialogType: Int? = DLG_NONE,
) : InstallStage(STAGE_ABORTED) {
companion object {
const val ABORT_REASON_INTERNAL_ERROR = 0
const val ABORT_REASON_POLICY = 1
const val ABORT_REASON_DONE = 2
+ const val DLG_NONE = 0
const val DLG_PACKAGE_ERROR = 1
}
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
index c109fc673ec4..1d4d1786c761 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -34,6 +34,8 @@ interface InstallActionListener {
*/
fun onNegativeResponse(stageCode: Int)
+ fun onNegativeResponse(resultCode: Int, data: Intent?)
+
/**
* Launch the intent to open the newly installed / updated app.
*/
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index 2b610d7b06f5..6f8eca3655b5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -36,10 +36,10 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import com.android.packageinstaller.R
-import com.android.packageinstaller.v2.model.InstallRepository
import com.android.packageinstaller.v2.model.InstallAborted
import com.android.packageinstaller.v2.model.InstallFailed
import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallRepository
import com.android.packageinstaller.v2.model.InstallStage
import com.android.packageinstaller.v2.model.InstallSuccess
import com.android.packageinstaller.v2.model.InstallUserActionRequired
@@ -50,6 +50,7 @@ import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.ParseErrorFragment
import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
import com.android.packageinstaller.v2.viewmodel.InstallViewModel
import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
@@ -124,8 +125,15 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
InstallStage.STAGE_ABORTED -> {
val aborted = installStage as InstallAborted
when (aborted.abortReason) {
- InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
- setResult(aborted.activityResultCode, aborted.resultIntent, true)
+ InstallAborted.ABORT_REASON_DONE,
+ InstallAborted.ABORT_REASON_INTERNAL_ERROR -> {
+ if (aborted.errorDialogType == InstallAborted.DLG_PACKAGE_ERROR) {
+ val parseErrorDialog = ParseErrorFragment(aborted)
+ showDialogInner(parseErrorDialog)
+ } else {
+ setResult(aborted.activityResultCode, aborted.resultIntent, true)
+ }
+ }
InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
else -> setResult(Activity.RESULT_CANCELED, null, true)
@@ -204,7 +212,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
// Don't finish the package installer app since the next dialog
// will be shown by this app
- shouldFinish = blockedByPolicyDialog != null
+ shouldFinish = blockedByPolicyDialog == null
showDialogInner(blockedByPolicyDialog)
}
setResult(Activity.RESULT_CANCELED, null, shouldFinish)
@@ -267,6 +275,10 @@ class InstallLaunch : FragmentActivity(), InstallActionListener {
setResult(Activity.RESULT_CANCELED, null, true)
}
+ override fun onNegativeResponse(resultCode: Int, data: Intent?) {
+ setResult(resultCode, data, true)
+ }
+
override fun sendUnknownAppsIntent(sourcePackageName: String) {
val settingsIntent = Intent()
settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
new file mode 100644
index 000000000000..68d48d62a0b6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
@@ -0,0 +1,64 @@
+/*
+ * 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.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.InstallAborted;
+import com.android.packageinstaller.v2.ui.InstallActionListener;
+
+public class ParseErrorFragment extends DialogFragment {
+
+ private static final String TAG = ParseErrorFragment.class.getSimpleName();
+ private final InstallAborted mDialogData;
+ private InstallActionListener mInstallActionListener;
+
+ public ParseErrorFragment(InstallAborted dialogData) {
+ mDialogData = dialogData;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ mInstallActionListener = (InstallActionListener) context;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(R.string.Parse_error_dlg_text)
+ .setPositiveButton(R.string.ok,
+ (dialog, which) ->
+ mInstallActionListener.onNegativeResponse(
+ mDialogData.getActivityResultCode(), mDialogData.getResultIntent()))
+ .create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+ mInstallActionListener.onNegativeResponse(
+ mDialogData.getActivityResultCode(), mDialogData.getResultIntent());
+ }
+}
diff --git a/packages/SettingsLib/LintChecker/Android.bp b/packages/SettingsLib/LintChecker/Android.bp
new file mode 100644
index 000000000000..eb489b1de380
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+ name: "SettingsLibLintChecker",
+ srcs: ["src/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
new file mode 100644
index 000000000000..1f062619b261
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotated
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class NullabilityAnnotationsDetector : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ if (!context.isJavaFile()) return null
+
+ return object : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (node.isPublic() && node.name != ANONYMOUS_CONSTRUCTOR) {
+ node.verifyMethod()
+ node.verifyMethodParameters()
+ }
+ }
+
+ private fun UMethod.isPublic() = modifierList.hasModifierProperty(PsiModifier.PUBLIC)
+
+ private fun UMethod.verifyMethod() {
+ if (isConstructor) return
+ if (returnType.isPrimitive()) return
+ checkAnnotation(METHOD_MSG)
+ }
+
+ private fun UMethod.verifyMethodParameters() {
+ for (parameter in uastParameters) {
+ if (parameter.type.isPrimitive()) continue
+ parameter.checkAnnotation(PARAMETER_MSG)
+ }
+ }
+
+ private fun PsiType?.isPrimitive() = this is PsiPrimitiveType
+
+ private fun UAnnotated.checkAnnotation(message: String) {
+ val oldAnnotation = findOldNullabilityAnnotation()
+ val oldAnnotationName = oldAnnotation?.qualifiedName?.substringAfterLast('.')
+
+ if (oldAnnotationName != null) {
+ val annotation = "androidx.annotation.$oldAnnotationName"
+ reportIssue(
+ REQUIRE_NULLABILITY_ISSUE,
+ "Prefer $annotation",
+ LintFix.create()
+ .replace()
+ .range(context.getLocation(oldAnnotation))
+ .with("@$annotation")
+ .autoFix()
+ .build()
+ )
+ } else if (!hasNullabilityAnnotation()) {
+ reportIssue(REQUIRE_NULLABILITY_ISSUE, message)
+ }
+ }
+
+ private fun UElement.reportIssue(
+ issue: Issue,
+ message: String,
+ quickfixData: LintFix? = null,
+ ) {
+ context.report(
+ issue = issue,
+ scope = this,
+ location = context.getNameLocation(this),
+ message = message,
+ quickfixData = quickfixData,
+ )
+ }
+
+ private fun UAnnotated.findOldNullabilityAnnotation() =
+ uAnnotations.find { it.qualifiedName in oldAnnotations }
+
+ private fun UAnnotated.hasNullabilityAnnotation() =
+ uAnnotations.any { it.qualifiedName in validAnnotations }
+ }
+ }
+
+ private fun JavaContext.isJavaFile() = psiFile?.fileElementType.toString().startsWith("java")
+
+ companion object {
+ private val validAnnotations = arrayOf("androidx.annotation.NonNull",
+ "androidx.annotation.Nullable")
+
+ private val oldAnnotations = arrayOf("android.annotation.NonNull",
+ "android.annotation.Nullable",
+ )
+
+ private const val ANONYMOUS_CONSTRUCTOR = "<anon-init>"
+
+ private const val METHOD_MSG =
+ "Java public method return with non-primitive type must add androidx annotation. " +
+ "Example: @NonNull | @Nullable Object functionName() {}"
+
+ private const val PARAMETER_MSG =
+ "Java public method parameter with non-primitive type must add androidx " +
+ "annotation. Example: functionName(@NonNull Context context, " +
+ "@Nullable Object obj) {}"
+
+ internal val REQUIRE_NULLABILITY_ISSUE = Issue
+ .create(
+ id = "RequiresNullabilityAnnotation",
+ briefDescription = "Requires nullability annotation for function",
+ explanation = "All public java APIs should specify nullability annotations for " +
+ "methods and parameters.",
+ category = Category.CUSTOM_LINT_CHECKS,
+ priority = 3,
+ severity = Severity.WARNING,
+ androidSpecific = true,
+ implementation = Implementation(
+ NullabilityAnnotationsDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
new file mode 100644
index 000000000000..e0ab24afee5a
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+class SettingsLintIssueRegistry : IssueRegistry() {
+ override val issues = listOf(NullabilityAnnotationsDetector.REQUIRE_NULLABILITY_ISSUE)
+
+ override val api: Int = CURRENT_API
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index f44b16104f99..0e40db23c66c 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -14,10 +14,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources>
+<resources
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearance.PreferenceTitle.SettingsLib"
parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:textColor">@color/settingslib_text_color_primary</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20sp</item>
</style>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index c143390f269c..b7f2c1e583f6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -34,6 +34,15 @@ object SettingsDimension {
end = itemPaddingEnd,
bottom = itemPaddingVertical,
)
+ val textFieldPadding = PaddingValues(
+ start = itemPaddingStart,
+ end = itemPaddingEnd,
+ )
+ val menuFieldPadding = PaddingValues(
+ start = itemPaddingStart,
+ end = itemPaddingEnd,
+ bottom = itemPaddingVertical,
+ )
val itemPaddingAround = 8.dp
val itemDividerHeight = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
index 0d6c064998ae..f6692a356899 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
@@ -51,7 +51,7 @@ fun SettingsExposedDropdownMenuBox(
onExpandedChange = { expanded = it },
modifier = Modifier
.width(350.dp)
- .padding(SettingsDimension.itemPadding),
+ .padding(SettingsDimension.menuFieldPadding),
) {
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
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 5d248e192c7a..ba8e354fa0c6 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
@@ -63,7 +63,7 @@ fun SettingsExposedDropdownMenuCheckBox(
onExpandedChange = { expanded = it },
modifier = Modifier
.width(350.dp)
- .padding(SettingsDimension.itemPadding)
+ .padding(SettingsDimension.menuFieldPadding)
.onSizeChanged { dropDownWidth = it.width },
) {
OutlinedTextField(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index e0dd4e17ce38..2ce3c66796db 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -42,7 +42,7 @@ fun SettingsOutlinedTextField(
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
- .padding(SettingsDimension.itemPadding),
+ .padding(SettingsDimension.textFieldPadding),
value = value,
onValueChange = onTextChange,
label = {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
index 0757df347d68..3102a00a24fd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -52,7 +52,7 @@ fun SettingsTextFieldPassword(
var visibility by remember { mutableStateOf(false) }
OutlinedTextField(
modifier = Modifier
- .padding(SettingsDimension.itemPadding)
+ .padding(SettingsDimension.menuFieldPadding)
.fillMaxWidth(),
value = value,
onValueChange = onTextChange,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index ebcca42bb588..59254925dbcf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -184,10 +184,6 @@ public class LocalMediaManager implements BluetoothCallback {
return false;
}
- if (mCurrentConnectedDevice != null) {
- mCurrentConnectedDevice.disconnect();
- }
-
device.setState(MediaDeviceState.STATE_CONNECTING);
mInfoMediaManager.connectToDevice(device);
return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index f2d9d1493c74..0c4cf769ca90 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -396,12 +396,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
}
/**
- * Stop transfer MediaDevice
- */
- public void disconnect() {
- }
-
- /**
* Set current device's state
*/
public void setState(@LocalMediaManager.MediaDeviceState int state) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 999e8d508e19..9a7d4f1540df 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -147,7 +147,6 @@ public class LocalMediaManagerTest {
mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
- verify(currentDevice).disconnect();
verify(mInfoMediaManager).connectToDevice(device);
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 2e39adc8f79e..add313419c7d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -93,6 +93,7 @@ public class GlobalSettings {
Settings.Global.Wearable.CLOCKWORK_AUTO_TIME,
Settings.Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE,
Settings.Global.Wearable.CLOCKWORK_24HR_TIME,
+ Settings.Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED,
Settings.Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED,
Settings.Global.Wearable.AMBIENT_ENABLED,
Settings.Global.Wearable.AMBIENT_TILT_TO_WAKE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 502239513002..c0a076095434 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -450,6 +450,8 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index bd99a8bbb09f..74fd828f97ea 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -99,7 +99,6 @@ final class WritableNamespacePrefixes {
"kiwi",
"latency_tracker",
"launcher",
- "launcher_lily",
"leaked_animator",
"lmkd_native",
"location",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 477c42e01fba..507d9c467d68 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -810,6 +810,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
<!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 42107b7a3182..d3a89f447d1f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -157,7 +157,7 @@ android_library {
"SystemUI-res",
"WifiTrackerLib",
"WindowManager-Shell",
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"SystemUICommon",
"SystemUICustomizationLib",
"SystemUILogLib",
@@ -274,7 +274,7 @@ android_library {
static_libs: [
"SystemUI-res",
"WifiTrackerLib",
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
"SystemUICustomizationLib",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a03fa9b39bfc..7443e4ccf79e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -84,6 +84,7 @@
<uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
<uses-permission android:name="android.permission.NETWORK_FACTORY" />
+ <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index 1ad16667f317..d0e6b2865891 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -19,4 +19,11 @@ flag {
namespace: "systemui"
description: "Enable Predictive Back Animation in Bouncer"
bug: "309545085"
+}
+
+flag {
+ name: "predictive_back_animate_dialogs"
+ namespace: "systemui"
+ description: "Enable Predictive Back Animation for SysUI dialogs"
+ bug: "309545085"
} \ No newline at end of file
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8438051e3430..872187abb9db 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -23,7 +23,7 @@ package {
android_library {
- name: "SystemUIAnimationLib",
+ name: "PlatformAnimationLib",
use_resource_processor: true,
srcs: [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index b738e2bc972b..efdbfdb83c70 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -494,7 +494,7 @@ constructor(
}
for (i in 0 until drawable.numberOfLayers) {
- (drawable.getDrawable(i) as? GradientDrawable)?.cornerRadii = radii
+ applyBackgroundRadii(drawable.getDrawable(i), radii)
}
}
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 42d088f218a1..9a4347d2afe4 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,7 +30,7 @@ android_library {
],
static_libs: [
- "SystemUIAnimationLib",
+ "PlatformAnimationLib",
"androidx.compose.runtime_runtime",
"androidx.compose.material3_material3",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8bd5ddb060c3..d20154437e02 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,13 +20,13 @@ import android.appwidget.AppWidgetHostView
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
-import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -45,6 +45,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
@@ -52,6 +53,7 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -72,6 +74,7 @@ import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -100,7 +103,8 @@ fun CommunalHub(
var isDraggingToRemove by remember { mutableStateOf(false) }
Box(
- modifier = modifier.fillMaxSize().background(Color.White),
+ modifier =
+ modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
) {
CommunalHubLazyGrid(
communalContent = communalContent,
@@ -111,7 +115,8 @@ fun CommunalHub(
isDraggingToRemove =
checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
isDraggingToRemove
- }
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
)
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -148,13 +153,14 @@ private fun BoxScope.CommunalHubLazyGrid(
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
+ onOpenWidgetPicker: (() -> Unit)? = null,
) {
var gridModifier = Modifier.align(Alignment.CenterStart)
val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
- val contentListState = rememberContentListState(communalContent, viewModel)
+ val contentListState = rememberContentListState(list, viewModel)
list = contentListState.list
// for drag & drop operations within the communal hub grid
dragDropState =
@@ -207,17 +213,16 @@ private fun BoxScope.CommunalHubLazyGrid(
if (viewModel.isEditMode && dragDropState != null) {
DraggableItem(
dragDropState = dragDropState,
- enabled = true,
+ enabled = list[index] is CommunalContentModel.Widget,
index = index,
size = size
- ) { isDragging ->
- val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
+ ) { _ ->
CommunalContent(
modifier = cardModifier,
- elevation = elevation,
model = list[index],
viewModel = viewModel,
size = size,
+ onOpenWidgetPicker = onOpenWidgetPicker,
)
}
} else {
@@ -258,16 +263,11 @@ private fun Toolbar(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- val buttonContentPadding =
- PaddingValues(
- vertical = Dimensions.ToolbarButtonPaddingVertical,
- horizontal = Dimensions.ToolbarButtonPaddingHorizontal,
- )
val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
Button(
onClick = onOpenWidgetPicker,
- colors = filledSecondaryButtonColors(),
- contentPadding = buttonContentPadding
+ colors = filledButtonColors(),
+ contentPadding = Dimensions.ButtonPadding
) {
Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
Spacer(spacerModifier)
@@ -276,25 +276,40 @@ private fun Toolbar(
)
}
- val buttonColors =
- if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors()
- OutlinedButton(
- onClick = {},
- colors = buttonColors,
- contentPadding = buttonContentPadding,
- modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) },
- ) {
- Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
- Spacer(spacerModifier)
- Text(
- text = stringResource(R.string.button_to_remove_widget),
- )
+ val colors = LocalAndroidColorScheme.current
+ if (isDraggingToRemove) {
+ Button(
+ // Button is disabled to make it non-clickable
+ enabled = false,
+ onClick = {},
+ colors =
+ ButtonDefaults.buttonColors(
+ disabledContainerColor = colors.primary,
+ disabledContentColor = colors.onPrimary,
+ ),
+ contentPadding = Dimensions.ButtonPadding,
+ modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ ) {
+ RemoveButtonContent(spacerModifier)
+ }
+ } else {
+ OutlinedButton(
+ // Button is disabled to make it non-clickable
+ enabled = false,
+ onClick = {},
+ colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+ border = BorderStroke(width = 1.0.dp, color = colors.primary),
+ contentPadding = Dimensions.ButtonPadding,
+ modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ ) {
+ RemoveButtonContent(spacerModifier)
+ }
}
Button(
onClick = onEditDone,
colors = filledButtonColors(),
- contentPadding = buttonContentPadding
+ contentPadding = Dimensions.ButtonPadding
) {
Text(
text = stringResource(R.string.hub_mode_editing_exit_button_text),
@@ -304,20 +319,20 @@ private fun Toolbar(
}
@Composable
-private fun filledButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
- return ButtonDefaults.buttonColors(
- containerColor = colors.primary,
- contentColor = colors.onPrimary,
+private fun RemoveButtonContent(spacerModifier: Modifier) {
+ Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+ Spacer(spacerModifier)
+ Text(
+ text = stringResource(R.string.button_to_remove_widget),
)
}
@Composable
-private fun filledSecondaryButtonColors(): ButtonColors {
+private fun filledButtonColors(): ButtonColors {
val colors = LocalAndroidColorScheme.current
return ButtonDefaults.buttonColors(
- containerColor = colors.secondary,
- contentColor = colors.onSecondary,
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
)
}
@@ -327,11 +342,15 @@ private fun CommunalContent(
viewModel: BaseCommunalViewModel,
size: SizeF,
modifier: Modifier = Modifier,
- elevation: Dp = 0.dp,
+ onOpenWidgetPicker: (() -> Unit)? = null,
) {
when (model) {
- is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, elevation, modifier)
+ is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+ is CommunalContentModel.CtaTileInViewMode ->
+ CtaTileInViewModeContent(viewModel, size, modifier)
+ is CommunalContentModel.CtaTileInEditMode ->
+ CtaTileInEditModeContent(size, modifier, onOpenWidgetPicker)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -349,17 +368,125 @@ fun WidgetPlaceholderContent(size: SizeF) {
) {}
}
+/** Presents a CTA tile at the end of the grid, to customize the hub. */
+@Composable
+private fun CtaTileInViewModeContent(
+ viewModel: BaseCommunalViewModel,
+ size: SizeF,
+ modifier: Modifier = Modifier,
+) {
+ val colors = LocalAndroidColorScheme.current
+ Card(
+ modifier = modifier.height(size.height.dp),
+ colors =
+ CardDefaults.cardColors(
+ containerColor = colors.primary,
+ contentColor = colors.onPrimary,
+ ),
+ shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+ modifier = Modifier.size(Dimensions.IconSize),
+ )
+ Text(
+ text = stringResource(R.string.cta_label_to_edit_widget),
+ style = MaterialTheme.typography.titleLarge,
+ textAlign = TextAlign.Center,
+ )
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ OutlinedButton(
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = colors.onPrimary,
+ ),
+ border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+ contentPadding = Dimensions.ButtonPadding,
+ onClick = viewModel::onDismissCtaTile,
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_dismiss),
+ )
+ }
+ Spacer(modifier = Modifier.size(Dimensions.Spacing))
+ Button(
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryContainer,
+ contentColor = colors.onPrimaryContainer,
+ ),
+ contentPadding = Dimensions.ButtonPadding,
+ onClick = viewModel::onOpenWidgetEditor
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ )
+ }
+ }
+ }
+ }
+}
+
+/** Presents a CTA tile at the end of the hub in edit mode, to add more widgets. */
+@Composable
+private fun CtaTileInEditModeContent(
+ size: SizeF,
+ modifier: Modifier = Modifier,
+ onOpenWidgetPicker: (() -> Unit)? = null,
+) {
+ if (onOpenWidgetPicker == null) {
+ throw IllegalArgumentException("onOpenWidgetPicker should not be null.")
+ }
+ val colors = LocalAndroidColorScheme.current
+ Card(
+ modifier = modifier.height(size.height.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(1.dp, colors.primary),
+ shape = RoundedCornerShape(200.dp),
+ onClick = onOpenWidgetPicker,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement =
+ Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+ tint = colors.primary,
+ modifier = Modifier.size(Dimensions.IconSize),
+ )
+ Text(
+ text = stringResource(R.string.cta_label_to_open_widget_picker),
+ style = MaterialTheme.typography.titleLarge,
+ color = colors.primary,
+ textAlign = TextAlign.Center,
+ )
+ }
+ }
+}
+
@Composable
private fun WidgetContent(
viewModel: BaseCommunalViewModel,
model: CommunalContentModel.Widget,
size: SizeF,
- elevation: Dp,
modifier: Modifier = Modifier,
) {
- Card(
+ Box(
modifier = modifier.height(size.height.dp),
- elevation = CardDefaults.cardElevation(draggedElevation = elevation),
+ contentAlignment = Alignment.Center,
) {
AndroidView(
modifier = modifier,
@@ -502,4 +629,10 @@ object Dimensions {
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
val ToolbarButtonSpaceBetween = 8.dp
+ val ButtonPadding =
+ PaddingValues(
+ vertical = ToolbarButtonPaddingVertical,
+ horizontal = ToolbarButtonPaddingHorizontal,
+ )
+ val IconSize = 48.dp
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 54c5de710f77..35a5054cbd2a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -487,7 +487,7 @@ class ElementTest {
// page should be composed.
HorizontalPager(
pagerState,
- beyondBoundsPageCount = 0,
+ outOfBoundsPageCount = 0,
) { page ->
when (page) {
0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 927fd8ea6279..1d1849680040 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -30,8 +30,8 @@ android_library {
"src/**/*.aidl",
],
static_libs: [
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 36aa4416f292..cec2d7459817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -496,7 +496,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};
final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};
final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
- sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
+ sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0],
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
for (int i1 = 0; i1 <= 1; ++i1) {
for (int i2 = 0; i2 <= 1; ++i2) {
@@ -505,7 +506,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
for (int i5 = 0; i5 <= 1; ++i5) {
final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
sensorBounds[i1], sensorBounds[i1], displayWidth[i2],
- displayHeight[i3], scaleFactor[i4], rotation[i5]);
+ displayHeight[i3], scaleFactor[i4], rotation[i5],
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
if (newParams.equals(oldParams)) {
continue;
@@ -549,7 +551,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Initialize the overlay.
mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, rotation));
+ scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
// Show the overlay.
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
@@ -560,7 +562,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Update overlay with the same parameters.
mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, rotation));
+ scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
mFgExecutor.runAllReady();
// Ensure the overlay was not recreated.
@@ -642,7 +644,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_0
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_0));
+ scaleFactor, Surface.ROTATION_0,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
@@ -657,7 +660,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_90));
+ scaleFactor, Surface.ROTATION_90,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
@@ -671,7 +675,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_270));
+ scaleFactor, Surface.ROTATION_270,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricExecutor.runAllReady();
@@ -685,7 +690,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
mUdfpsController.updateOverlayParams(testParams.sensorProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
- scaleFactor, Surface.ROTATION_180));
+ scaleFactor, Surface.ROTATION_180,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 455f9865edf3..92b75cb0f47d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -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.
@@ -25,13 +25,13 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -59,74 +59,62 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun mediaPlaying_defaultsToFalse() =
+ fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@Test
- fun mediaPlaying_emitsInitialValue() =
+ fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
- runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- }
-
- @Test
- fun mediaPlaying_updatesWhenMediaDataLoaded() =
- testScope.runTest {
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+ // Listener is added
+ verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
// Initial value is false.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
-
- // Listener is added
- verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
// Change to media available and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns true.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
runCurrent()
- assertThat(isMediaPlaying()).isTrue()
+
+ // Media active now returns true.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
}
@Test
- fun mediaPlaying_updatesWhenMediaDataRemoved() =
+ fun mediaModel_updatesWhenMediaDataRemoved() =
testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
- // Initial value is true.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ // Listener is added
+ verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
+ // Change to media available and notify the listener.
+ whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- // Listener is added.
- verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+ // Media active now returns true.
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns false.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+
+ // Media active now returns false.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 449ee6f414dd..4079f1241f31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -19,14 +19,14 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
-import android.content.BroadcastReceiver
import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.ACTION_USER_UNLOCKED
import android.os.UserHandle
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
@@ -38,15 +38,12 @@ import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -55,8 +52,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -70,8 +67,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var appWidgetHost: AppWidgetHost
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
-
@Mock private lateinit var userManager: UserManager
@Mock private lateinit var userHandle: UserHandle
@@ -125,10 +120,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
communalEnabled(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
+ repository.communalWidgets.launchIn(backgroundScope)
runCurrent()
- verify(communalWidgetDao, Mockito.never()).getWidgets()
+ verify(communalWidgetDao, never()).getWidgets()
}
@Test
@@ -136,10 +131,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
+ repository.communalWidgets.launchIn(backgroundScope)
runCurrent()
- verify(communalWidgetDao, Mockito.never()).getWidgets()
+ verify(communalWidgetDao, never()).getWidgets()
}
@Test
@@ -147,8 +142,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- val communalWidgets = collectLastValue(repository.communalWidgets)
- communalWidgets()
+ val communalWidgets by collectLastValue(repository.communalWidgets)
runCurrent()
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
@@ -158,11 +152,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
userUnlocked(true)
installedProviders(listOf(stopwatchProviderInfo))
- broadcastReceiverUpdate()
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(ACTION_USER_UNLOCKED)
+ )
runCurrent()
verify(communalWidgetDao).getWidgets()
- assertThat(communalWidgets())
+ assertThat(communalWidgets)
.containsExactly(
CommunalWidgetContentModel(
appWidgetId = communalWidgetItemEntry.widgetId,
@@ -182,9 +179,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
+ whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
- repository.addWidget(provider, priority)
+ repository.addWidget(provider, priority) { true }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,75 +190,117 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun deleteWidget_removeWidgetId_andDeleteFromDb() =
+ fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
userUnlocked(true)
val repository = initCommunalWidgetRepository()
runCurrent()
+ val provider = ComponentName("pkg_name", "cls_name")
val id = 1
- repository.deleteWidget(id)
+ val priority = 1
+ whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+ whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
+ repository.addWidget(provider, priority) { false }
runCurrent()
- verify(communalWidgetDao).deleteWidgetById(id)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao, never()).addWidget(id, provider, priority)
verify(appWidgetHost).deleteAppWidgetId(id)
}
@Test
- fun reorderWidgets_queryDb() =
+ fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
userUnlocked(true)
val repository = initCommunalWidgetRepository()
runCurrent()
- val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- repository.updateWidgetOrder(widgetIdToPriorityMap)
+ val provider = ComponentName("pkg_name", "cls_name")
+ val id = 1
+ val priority = 1
+ whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
+ whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
+ repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(appWidgetHost).deleteAppWidgetId(id)
}
@Test
- fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- communalEnabled(false)
+ userUnlocked(true)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverNeverRegistered()
+ runCurrent()
+
+ val provider = ComponentName("pkg_name", "cls_name")
+ val id = 1
+ val priority = 1
+ whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(false)
+ whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
+ .thenReturn(id)
+ var configured = false
+ repository.addWidget(provider, priority) {
+ configured = true
+ true
+ }
+ runCurrent()
+
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider)
+ verify(communalWidgetDao).addWidget(id, provider, priority)
+ assertThat(configured).isFalse()
}
@Test
- fun broadcastReceiver_featureEnabledAndUserUnlocked_doNotRegisterBroadcastReceiver() =
+ fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
userUnlocked(true)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverNeverRegistered()
+ runCurrent()
+
+ val id = 1
+ repository.deleteWidget(id)
+ runCurrent()
+
+ verify(communalWidgetDao).deleteWidgetById(id)
+ verify(appWidgetHost).deleteAppWidgetId(id)
}
@Test
- fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
+ fun reorderWidgets_queryDb() =
testScope.runTest {
- userUnlocked(false)
+ userUnlocked(true)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verifyBroadcastReceiverRegistered()
+ runCurrent()
+
+ val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ repository.updateWidgetOrder(widgetIdToPriorityMap)
+ runCurrent()
+
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
- fun broadcastReceiver_whenFlowFinishes_unregisterBroadcastReceiver() =
+ fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
testScope.runTest {
- userUnlocked(false)
+ communalEnabled(false)
val repository = initCommunalWidgetRepository()
-
- val job = launch { repository.communalWidgets.collect() }
+ repository.communalWidgets.launchIn(backgroundScope)
runCurrent()
- val receiver = broadcastReceiverUpdate()
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
+ }
- job.cancel()
+ @Test
+ fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
+ testScope.runTest {
+ userUnlocked(false)
+ val repository = initCommunalWidgetRepository()
+ repository.communalWidgets.launchIn(backgroundScope)
runCurrent()
-
- verify(broadcastDispatcher).unregisterReceiver(receiver)
+ assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
}
@Test
@@ -268,12 +308,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
- verify(appWidgetHost, Mockito.never()).startListening()
+ repository.communalWidgets.launchIn(backgroundScope)
+ runCurrent()
+ verify(appWidgetHost, never()).startListening()
userUnlocked(true)
- broadcastReceiverUpdate()
- collectLastValue(repository.communalWidgets)()
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(ACTION_USER_UNLOCKED)
+ )
+ runCurrent()
verify(appWidgetHost).startListening()
}
@@ -283,18 +327,25 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
- collectLastValue(repository.communalWidgets)()
+ repository.communalWidgets.launchIn(backgroundScope)
+ runCurrent()
userUnlocked(true)
- broadcastReceiverUpdate()
- collectLastValue(repository.communalWidgets)()
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(ACTION_USER_UNLOCKED)
+ )
+ runCurrent()
verify(appWidgetHost).startListening()
- verify(appWidgetHost, Mockito.never()).stopListening()
+ verify(appWidgetHost, never()).stopListening()
userUnlocked(false)
- broadcastReceiverUpdate()
- collectLastValue(repository.communalWidgets)()
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(ACTION_USER_UNLOCKED)
+ )
+ runCurrent()
verify(appWidgetHost).stopListening()
}
@@ -305,7 +356,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
appWidgetHost,
testScope.backgroundScope,
testDispatcher,
- broadcastDispatcher,
+ fakeBroadcastDispatcher,
communalRepository,
communalWidgetHost,
communalWidgetDao,
@@ -315,45 +366,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
)
}
- private fun verifyBroadcastReceiverRegistered() {
- verify(broadcastDispatcher)
- .registerReceiver(
- any(),
- any(),
- nullable(),
- nullable(),
- anyInt(),
- nullable(),
- )
- }
-
- private fun verifyBroadcastReceiverNeverRegistered() {
- verify(broadcastDispatcher, Mockito.never())
- .registerReceiver(
- any(),
- any(),
- nullable(),
- nullable(),
- anyInt(),
- nullable(),
- )
- }
-
- private fun broadcastReceiverUpdate(): BroadcastReceiver {
- val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
- verify(broadcastDispatcher)
- .registerReceiver(
- broadcastReceiverCaptor.capture(),
- any(),
- nullable(),
- nullable(),
- anyInt(),
- nullable(),
- )
- broadcastReceiverCaptor.value.onReceive(null, null)
- return broadcastReceiverCaptor.value
- }
-
private fun communalEnabled(enabled: Boolean) {
communalRepository.setIsCommunalEnabled(enabled)
}
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 62084aa0d981..744b65f20592 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
@@ -148,25 +148,29 @@ class CommunalInteractorTest : SysuiTestCase() {
whenever(target1.smartspaceTargetId).thenReturn("target1")
whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target1.creationTimeMillis).thenReturn(0L)
// Does not have RemoteViews
val target2 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target2")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(null)
+ whenever(target2.smartspaceTargetId).thenReturn("target2")
+ whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target2.remoteViews).thenReturn(null)
+ whenever(target2.creationTimeMillis).thenReturn(0L)
// Timer and has RemoteViews
val target3 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target3")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.smartspaceTargetId).thenReturn("target3")
+ whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.creationTimeMillis).thenReturn(0L)
val targets = listOf(target1, target2, target3)
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(1)
- assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
+ assertThat(smartspaceContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
}
@Test
@@ -256,16 +260,12 @@ class CommunalInteractorTest : SysuiTestCase() {
val targets = mutableListOf<SmartspaceTarget>()
for (index in 0 until totalTargets) {
- val target = mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target$index")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
- targets.add(target)
+ targets.add(smartspaceTimer(index.toString()))
}
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
for (index in 0 until totalTargets) {
assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -279,13 +279,77 @@ class CommunalInteractorTest : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// Media is playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
- val umoContent by collectLastValue(underTest.umoContent)
+ val umoContent by collectLastValue(underTest.ongoingContent)
assertThat(umoContent?.size).isEqualTo(1)
assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
- assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
+ assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+ }
+
+ @Test
+ fun ongoing_shouldOrderAndSizeByTimestamp() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Timer1 started
+ val timer1 = smartspaceTimer("timer1", timestamp = 1L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))
+
+ // Umo started
+ mediaRepository.mediaActive(timestamp = 2L)
+
+ // Timer2 started
+ val timer2 = smartspaceTimer("timer2", timestamp = 3L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))
+
+ // Timer3 started
+ val timer3 = smartspaceTimer("timer3", timestamp = 4L)
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))
+
+ val ongoingContent by collectLastValue(underTest.ongoingContent)
+ assertThat(ongoingContent?.size).isEqualTo(4)
+ assertThat(ongoingContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
+ assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
+ assertThat(ongoingContent?.get(1)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
+ assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
+ assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+ assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
+ assertThat(ongoingContent?.get(3)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
+ assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
+ }
+
+ @Test
+ fun cta_visibilityTrue_shows() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(true)
+
+ val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+ assertThat(ctaTileContent?.size).isEqualTo(1)
+ assertThat(ctaTileContent?.get(0))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ assertThat(ctaTileContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.CTA_TILE_IN_VIEW_MODE_KEY)
+ }
+
+ @Test
+ fun ctaTile_visibilityFalse_doesNotShow() =
+ testScope.runTest {
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ communalRepository.setCtaTileInViewModeVisibility(false)
+
+ val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+ assertThat(ctaTileContent).isEmpty()
}
@Test
@@ -334,4 +398,13 @@ class CommunalInteractorTest : SysuiTestCase() {
underTest.showWidgetEditor()
verify(editWidgetsActivityStarter).startActivity()
}
+
+ private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
+ val timer = mock(SmartspaceTarget::class.java)
+ whenever(timer.smartspaceTargetId).thenReturn(id)
+ whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(timer.creationTimeMillis).thenReturn(timestamp)
+ return timer
+ }
}
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 f2f97054ea12..c638e1ea89ee 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
@@ -16,7 +16,11 @@
package com.android.systemui.communal.view.viewmodel
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
import android.app.smartspace.SmartspaceTarget
+import android.appwidget.AppWidgetHost
+import android.content.ComponentName
import android.os.PowerManager
import android.provider.Settings
import android.widget.RemoteViews
@@ -42,6 +46,8 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -50,12 +56,14 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var shadeViewController: ShadeViewController
@Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var appWidgetHost: AppWidgetHost
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -73,7 +81,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- val withDeps = CommunalInteractorFactory.create()
+ val withDeps = CommunalInteractorFactory.create(testScope)
keyguardRepository = withDeps.keyguardRepository
communalRepository = withDeps.communalRepository
tutorialRepository = withDeps.tutorialRepository
@@ -84,6 +92,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
underTest =
CommunalEditModeViewModel(
withDeps.communalInteractor,
+ appWidgetHost,
Provider { shadeViewController },
powerManager,
mediaHost,
@@ -91,7 +100,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
- fun communalContent_onlyWidgetsAreShownInEditMode() =
+ fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
@@ -119,16 +128,18 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
- // Only Widgets are shown.
- assertThat(communalContent?.size).isEqualTo(2)
+ // Only Widgets and CTA tile are shown.
+ assertThat(communalContent?.size).isEqualTo(3)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.Widget::class.java)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@Test
@@ -143,4 +154,53 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
)
.isEqualTo(false)
}
+
+ @Test
+ fun addingWidgetTriggersConfiguration() =
+ testScope.runTest {
+ val provider = ComponentName("pkg.test", "testWidget")
+ val widgetToConfigure by collectLastValue(underTest.widgetsToConfigure)
+ assertThat(widgetToConfigure).isNull()
+ underTest.onAddWidget(componentName = provider, priority = 0)
+ assertThat(widgetToConfigure).isEqualTo(1)
+ }
+
+ @Test
+ fun settingResultOkAddsWidget() =
+ testScope.runTest {
+ val provider = ComponentName("pkg.test", "testWidget")
+ val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
+ assertThat(widgetAdded).isNull()
+ underTest.onAddWidget(componentName = provider, priority = 0)
+ assertThat(widgetAdded).isNull()
+ underTest.setConfigurationResult(RESULT_OK)
+ assertThat(widgetAdded).isEqualTo(1)
+ }
+
+ @Test
+ fun settingResultCancelledDoesNotAddWidget() =
+ testScope.runTest {
+ val provider = ComponentName("pkg.test", "testWidget")
+ val widgetAdded by collectLastValue(widgetRepository.widgetAdded)
+ assertThat(widgetAdded).isNull()
+ underTest.onAddWidget(componentName = provider, priority = 0)
+ assertThat(widgetAdded).isNull()
+ underTest.setConfigurationResult(RESULT_CANCELED)
+ assertThat(widgetAdded).isNull()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun settingResultBeforeWidgetAddedThrowsException() {
+ underTest.setConfigurationResult(RESULT_OK)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun addingWidgetWhileConfigurationActiveFails() =
+ testScope.runTest {
+ val providerOne = ComponentName("pkg.test", "testWidget")
+ underTest.onAddWidget(componentName = providerOne, priority = 0)
+ runCurrent()
+ val providerTwo = ComponentName("pkg.test", "testWidget2")
+ underTest.onAddWidget(componentName = providerTwo, priority = 0)
+ }
}
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 182cc5d750bb..16e0bc00ad35 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
@@ -112,7 +112,7 @@ class CommunalViewModelTest : SysuiTestCase() {
}
@Test
- fun ordering_smartspaceBeforeUmoBeforeWidgets() =
+ fun ordering_smartspaceBeforeUmoBeforeWidgetsBeforeCtaTile() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
@@ -140,12 +140,15 @@ class CommunalViewModelTest : SysuiTestCase() {
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
+
+ // CTA Tile not dismissed.
+ communalRepository.setCtaTileInViewModeVisibility(true)
val communalContent by collectLastValue(underTest.communalContent)
- // Order is smart space, then UMO, then widget content.
- assertThat(communalContent?.size).isEqualTo(4)
+ // Order is smart space, then UMO, widget content and cta tile.
+ assertThat(communalContent?.size).isEqualTo(5)
assertThat(communalContent?.get(0))
.isInstanceOf(CommunalContentModel.Smartspace::class.java)
assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -153,5 +156,7 @@ class CommunalViewModelTest : SysuiTestCase() {
.isInstanceOf(CommunalContentModel.Widget::class.java)
assertThat(communalContent?.get(3))
.isInstanceOf(CommunalContentModel.Widget::class.java)
+ assertThat(communalContent?.get(4))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
}
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 6c4bb372bc3a..c4ebbdcc2f58 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
@@ -24,6 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.common.shared.model.Position
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.DozeMachine
@@ -71,6 +72,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private lateinit var systemClock: FakeSystemClock
+ private lateinit var facePropertyRepository: FakeFacePropertyRepository
private lateinit var underTest: KeyguardRepositoryImpl
@@ -78,6 +80,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
systemClock = FakeSystemClock()
+ facePropertyRepository = FakeFacePropertyRepository()
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
@@ -89,6 +92,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
mainDispatcher,
testScope.backgroundScope,
systemClock,
+ facePropertyRepository,
)
}
@@ -482,10 +486,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
-
- val captor = argumentCaptor<AuthController.Callback>()
runCurrent()
- verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
// one
@@ -500,8 +501,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
Point(250, 250),
)
.onEach {
- whenever(authController.faceSensorLocation).thenReturn(it)
- captor.value.onFaceSensorLocationChanged()
+ facePropertyRepository.setSensorLocation(it)
runCurrent()
}
.also { dispatchedSensorLocations ->
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 7c3dc972cfd0..5b88ebe69bfe 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
@@ -32,7 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
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.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.testKosmos
@@ -56,8 +56,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val screenOffAnimationController = kosmos.screenOffAnimationController
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val fakeNotificationsKeyguardViewStateRepository =
- kosmos.fakeNotificationsKeyguardViewStateRepository
+ private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
private val dozeParameters = kosmos.dozeParameters
private val underTest = kosmos.keyguardRootViewModel
@@ -118,7 +117,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
+ notificationsKeyguardInteractor.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -130,9 +129,9 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(true)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
assertThat(isVisible?.value).isTrue()
@@ -144,10 +143,10 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
assertThat(isVisible?.value).isTrue()
@@ -159,11 +158,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
assertThat(isVisible?.value).isTrue()
@@ -175,11 +174,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
assertThat(isVisible?.value).isTrue()
@@ -191,11 +190,11 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
runCurrent()
assertThat(isVisible?.isAnimating).isEqualTo(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 00405d0a07e7..c2ce39249f9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -29,27 +29,37 @@ import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.util.time.FakeSystemClock
import java.time.Instant
import java.time.LocalDateTime
import java.util.TimeZone
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlarmTileMapperTest : SysuiTestCase() {
+ private val oneMinute = 60000L
private val kosmos = Kosmos()
private val alarmTileConfig = kosmos.qsAlarmTileConfig
+ private val fakeClock = FakeSystemClock()
// Using lazy (versus =) to make sure we override the right context -- see b/311612168
private val mapper by lazy {
AlarmTileMapper(
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
+ fakeClock
)
}
+ @Before
+ fun setup() {
+ fakeClock.setCurrentTimeMillis(0) // same time both in test & map()
+ }
+
@Test
fun notAlarmSet() {
val inputModel = AlarmTileModel.NoAlarmSet
@@ -66,7 +76,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
@Test
fun nextAlarmSet24HourFormat() {
- val triggerTime = 1L
+ val triggerTime = fakeClock.currentTimeMillis() + oneMinute
val inputModel =
AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null))
@@ -85,7 +95,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
@Test
fun nextAlarmSet12HourFormat() {
- val triggerTime = 1L
+ val triggerTime = fakeClock.currentTimeMillis() + oneMinute
val inputModel =
AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
@@ -102,6 +112,66 @@ class AlarmTileMapperTest : SysuiTestCase() {
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
+ @Test
+ fun nextAlarmSetMoreThanAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+ val oneWeekAndOneMinute = 7 * 24 * 60 * 60 * 1000L + oneMinute
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeekAndOneMinute
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun nextAlarmSetOneMinuteLessThanAWeekLater_mapsSecondaryLabelToDisplayTime() {
+ val oneWeekMinusOneMinute = 7 * 24 * 60 * 60 * 1000L - oneMinute
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeekMinusOneMinute
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun nextAlarmSetExactlyAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+ val oneWeek = 7 * 24 * 60 * 60 * 1000L
+ val triggerTime = fakeClock.currentTimeMillis() + oneWeek
+ val inputModel =
+ AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+ val outputState = mapper.map(alarmTileConfig, inputModel)
+
+ val localDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(triggerTime),
+ TimeZone.getDefault().toZoneId()
+ )
+ val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+ val expectedState =
+ createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
private fun createAlarmTileState(
activationState: QSTileState.ActivationState,
secondaryLabel: String
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index de767e39499a..7274c0c65e69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -21,29 +21,30 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -61,17 +62,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class SystemUIDialogTest extends SysuiTestCase {
@Mock
- private FeatureFlags mFeatureFlags;
- @Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private SystemUIDialog.Delegate mDelegate;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
}
@@ -110,16 +111,13 @@ public class SystemUIDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS)
public void usePredictiveBackAnimFlag() {
- when(mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM))
- .thenReturn(true);
final SystemUIDialog dialog = new SystemUIDialog(mContext);
dialog.show();
assertTrue(dialog.isShowing());
- verify(mFeatureFlags, atLeast(1))
- .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
dialog.dismiss();
assertFalse(dialog.isShowing());
@@ -174,7 +172,6 @@ public class SystemUIDialogTest extends SysuiTestCase {
private SystemUIDialog createDialogWithDelegate() {
SystemUIDialog.Factory factory = new SystemUIDialog.Factory(
getContext(),
- mFeatureFlags,
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 75d1869adc7c..a9ee4055d1a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -68,6 +68,8 @@ class FakeMobileIconsInteractor(
override val isSingleCarrier = MutableStateFlow(true)
+ override val icons: MutableStateFlow<List<MobileIconInteractor>> = MutableStateFlow(emptyList())
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
@@ -80,8 +82,12 @@ class FakeMobileIconsInteractor(
override val isForceHidden = MutableStateFlow(false)
/** Always returns a new fake interactor */
- override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
- return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it }
+ override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor {
+ return FakeMobileIconInteractor(tableLogBuffer).also {
+ interactorCache[subId] = it
+ // Also update the icons
+ icons.value = interactorCache.values.toList()
+ }
}
/**
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 0537f17b3594..9063a02ee885 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -46,8 +46,8 @@ java_library {
static_libs: [
"androidx.annotation_annotation",
"androidx-constraintlayout_constraintlayout",
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUICommon",
"SystemUILogLib",
"androidx.annotation_annotation",
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 7ab44e70e6fe..73874a08b0bd 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -44,6 +44,8 @@
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center_vertical"
+ android:textDirection="locale"
+ android:textAlignment="viewStart"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status.Build"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index f4b0a45a8d32..84681d34be8e 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -89,7 +89,6 @@
android:textColorHint="@color/remote_input_hint"
android:textSize="16sp"
android:background="@null"
- android:maxLines="4"
android:ellipsize="start"
android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 10f7c4d3ee5b..64a1d248b221 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -538,15 +538,15 @@
-->
<string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera of outer display that needs extra protection -->
+ <!-- ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedCameraId"></string>
- <!-- Physical ID for the camera of outer display that needs extra protection -->
+ <!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera of inner display that needs extra protection -->
+ <!-- ID for the camera of inner display that needs extra protection. -->
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
@@ -650,13 +650,20 @@
<!-- Whether to use window background blur for the volume dialog. -->
<bool name="config_volumeDialogUseBackgroundBlur">false</bool>
- <!-- The properties of the face auth camera in pixels -->
+ <!-- The properties of the face auth front camera for outer display in pixels -->
<integer-array name="config_face_auth_props">
<!-- sensorLocationX -->
<!-- sensorLocationY -->
<!--sensorRadius -->
</integer-array>
+ <!-- The properties of the face auth front camera for inner display in pixels -->
+ <integer-array name="config_inner_face_auth_props">
+ <!-- sensorLocationX -->
+ <!-- sensorLocationY -->
+ <!--sensorRadius -->
+ </integer-array>
+
<!-- Overrides the behavior of the face unlock keyguard bypass setting:
0 - Don't override the setting (default)
1 - Override the setting to always bypass keyguard
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 854bb0f05c5e..3f11faebffb1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1079,6 +1079,14 @@
<!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
<string name="button_to_open_widget_editor">Open the widget editor</string>
+ <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
+ <string name="cta_tile_button_to_open_widget_editor">Customize</string>
+ <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
+ <string name="cta_tile_button_to_dismiss">Dismiss</string>
+ <!-- Label for CTA tile to edit the glanceable hub [CHAR LIMIT=100] -->
+ <string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
+ <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
+ <string name="cta_label_to_open_widget_picker">Add more widgets</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
<string name="button_to_remove_widget">Remove</string>
<!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 3a26ebff6c6a..05106c904d3d 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -51,8 +51,8 @@ android_library {
],
static_libs: [
"BiometricsSharedLib",
+ "PlatformAnimationLib",
"PluginCoreLib",
- "SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index 65c5a49b1135..e31fb89b5432 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.shared.model
import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintSensorProperties.SensorType
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
import android.view.Surface
import android.view.Surface.Rotation
@@ -39,6 +41,8 @@ import android.view.Surface.Rotation
* the native resolution.
*
* [rotation] current rotation of the display.
+ *
+ * [sensorType] fingerprint sensor type
*/
data class UdfpsOverlayParams(
val sensorBounds: Rect = Rect(),
@@ -46,7 +50,8 @@ data class UdfpsOverlayParams(
val naturalDisplayWidth: Int = 0,
val naturalDisplayHeight: Int = 0,
val scaleFactor: Float = 1f,
- @Rotation val rotation: Int = Surface.ROTATION_0
+ @Rotation val rotation: Int = Surface.ROTATION_0,
+ @SensorType val sensorType: Int = TYPE_UDFPS_OPTICAL
) {
/** Same as [sensorBounds], but in native resolution. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
index f005af3780cb..92473e84cbd9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.shared.navigationbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -125,7 +125,7 @@ public class KeyButtonRipple extends Drawable {
/**
* @param onInvisibleRunnable run after we are next drawn invisibly. Only used once.
*/
- void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
+ public void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
mOnInvisibleRunnable = onInvisibleRunnable;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
index a4b6451caaea..2145166e9bc5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -30,7 +30,7 @@ import android.widget.ImageView;
import androidx.annotation.DimenRes;
-import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
public class FloatingRotationButtonView extends ImageView {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
index 047ff75468ed..9f8220150b88 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -21,6 +21,11 @@ import androidx.lifecycle.Lifecycle.Event;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
+/**
+ * Implementation of the collection used and thread guarantees are left to the discretion of the
+ * client. Consider using {@link com.android.systemui.util.ListenerSet} to prevent concurrent
+ * modification exceptions.
+ */
public interface CallbackController<T> {
/** Add a callback */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e03c62783475..d6d5c2631e14 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -68,7 +68,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
-import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.DebugRoundedCornerDelegate;
@@ -92,6 +92,7 @@ import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import dalvik.annotation.optimization.NeverCompile;
@@ -131,8 +132,6 @@ public class ScreenDecorations implements
};
private final ScreenDecorationsLogger mLogger;
- private final AuthController mAuthController;
-
private DisplayTracker mDisplayTracker;
@VisibleForTesting
protected boolean mIsRegistered;
@@ -183,6 +182,9 @@ public class ScreenDecorations implements
private DisplayCutout mDisplayCutout;
private boolean mPendingManualConfigUpdate;
+ private FacePropertyRepository mFacePropertyRepository;
+ private JavaAdapter mJavaAdapter;
+
@VisibleForTesting
protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
if (mFaceScanningFactory.shouldShowFaceScanningAnim()) {
@@ -330,7 +332,8 @@ public class ScreenDecorations implements
PrivacyDotDecorProviderFactory dotFactory,
FaceScanningProviderFactory faceScanningFactory,
ScreenDecorationsLogger logger,
- AuthController authController) {
+ FacePropertyRepository facePropertyRepository,
+ JavaAdapter javaAdapter) {
mContext = context;
mSecureSettings = secureSettings;
mCommandRegistry = commandRegistry;
@@ -342,22 +345,10 @@ public class ScreenDecorations implements
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
mLogger = logger;
- mAuthController = authController;
+ mFacePropertyRepository = facePropertyRepository;
+ mJavaAdapter = javaAdapter;
}
-
- private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
- @Override
- public void onFaceSensorLocationChanged() {
- mLogger.onSensorLocationChanged();
- if (mExecutor != null) {
- mExecutor.execute(
- () -> updateOverlayProviderViews(
- new Integer[]{mFaceScanningViewId}));
- }
- }
- };
-
private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
// If we are exiting debug mode, we can set it (false) and bail, otherwise we will
// ensure that debug mode is set
@@ -407,7 +398,8 @@ public class ScreenDecorations implements
mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
mDotViewController.setUiExecutor(mExecutor);
- mAuthController.addCallback(mAuthControllerCallback);
+ mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
+ this::onFaceSensorLocationChanged);
mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
() -> new ScreenDecorCommand(mScreenDecorCommandCallback));
}
@@ -1320,6 +1312,16 @@ public class ScreenDecorations implements
view.setLayoutParams(params);
}
+ @VisibleForTesting
+ void onFaceSensorLocationChanged(Point location) {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(
+ new Integer[]{mFaceScanningViewId}));
+ }
+ }
+
public static class DisplayCutoutView extends DisplayCutoutBaseView {
final List<Rect> mBounds = new ArrayList();
final Rect mBoundingRect = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a4f90ebfb83c..093a1ffb4635 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -148,10 +148,6 @@ public class AuthController implements
private final Display mDisplay;
private float mScaleFactor = 1f;
- // sensor locations without any resolution scaling nor rotation adjustments:
- @Nullable private final Point mFaceSensorLocationDefault;
- // cached sensor locations:
- @Nullable private Point mFaceSensorLocation;
@Nullable private Point mFingerprintSensorLocation;
@Nullable private Rect mUdfpsBounds;
private final Set<Callback> mCallbacks = new HashSet<>();
@@ -622,7 +618,6 @@ public class AuthController implements
mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo);
updateUdfpsLocation();
updateFingerprintLocation();
- updateFaceLocation();
}
/**
* @return where the fingerprint sensor exists in pixels in its natural orientation.
@@ -682,31 +677,6 @@ public class AuthController implements
}
/**
- * @return where the face sensor exists in pixels in the current device orientation. Returns
- * null if no face sensor exists.
- */
- @Nullable public Point getFaceSensorLocation() {
- return mFaceSensorLocation;
- }
-
- private void updateFaceLocation() {
- if (mFaceProps == null || mFaceSensorLocationDefault == null) {
- mFaceSensorLocation = null;
- } else {
- mFaceSensorLocation = rotateToCurrentOrientation(
- new Point(
- (int) (mFaceSensorLocationDefault.x * mScaleFactor),
- (int) (mFaceSensorLocationDefault.y * mScaleFactor)),
- mCachedDisplayInfo
- );
- }
-
- for (final Callback cb : mCallbacks) {
- cb.onFaceSensorLocationChanged();
- }
- }
-
- /**
* @param inOutPoint point on the display in pixels. Going in, represents the point
* in the device's natural orientation. Going out, represents
* the point in the display's current orientation.
@@ -821,17 +791,7 @@ public class AuthController implements
mWakefulnessLifecycle = wakefulnessLifecycle;
mPanelInteractionDetector = panelInteractionDetector;
-
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
- int[] faceAuthLocation = context.getResources().getIntArray(
- com.android.systemui.res.R.array.config_face_auth_props);
- if (faceAuthLocation == null || faceAuthLocation.length < 2) {
- mFaceSensorLocationDefault = null;
- } else {
- mFaceSensorLocationDefault = new Point(
- faceAuthLocation[0],
- faceAuthLocation[1]);
- }
mDisplay = mContext.getDisplay();
updateSensorLocations();
@@ -868,7 +828,8 @@ public class AuthController implements
mCachedDisplayInfo.getNaturalWidth(),
mCachedDisplayInfo.getNaturalHeight(),
mScaleFactor,
- mCachedDisplayInfo.rotation);
+ mCachedDisplayInfo.rotation,
+ udfpsProp.sensorType);
mUdfpsController.updateOverlayParams(udfpsProp, mUdfpsOverlayParams);
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds) || !Objects.equals(
@@ -1358,8 +1319,6 @@ public class AuthController implements
final AuthDialog dialog = mCurrentDialog;
pw.println(" mCachedDisplayInfo=" + mCachedDisplayInfo);
pw.println(" mScaleFactor=" + mScaleFactor);
- pw.println(" faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault);
- pw.println(" faceAuthSensorLocation=" + getFaceSensorLocation());
pw.println(" fingerprintSensorLocationInNaturalOrientation="
+ getFingerprintSensorLocationInNaturalOrientation());
pw.println(" fingerprintSensorLocation=" + getFingerprintSensorLocation());
@@ -1433,11 +1392,5 @@ public class AuthController implements
* {@link #onFingerprintLocationChanged}.
*/
default void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {}
-
- /**
- * Called when the location of the face unlock sensor (typically the front facing camera)
- * changes. The location in pixels can change due to resolution changes.
- */
- default void onFaceSensorLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 45967c600a3c..86f372a94848 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,6 +32,7 @@ import com.android.keyguard.logging.KeyguardLogger
import com.android.settingslib.Utils
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.lightRevealMigration
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -80,6 +81,7 @@ class AuthRippleController @Inject constructor(
private val logger: KeyguardLogger,
private val biometricUnlockController: BiometricUnlockController,
private val lightRevealScrim: LightRevealScrim,
+ private val facePropertyRepository: FacePropertyRepository,
rippleView: AuthRippleView?
) :
ViewController<AuthRippleView>(rippleView),
@@ -263,7 +265,7 @@ class AuthRippleController @Inject constructor(
fun updateSensorLocation() {
fingerprintSensorLocation = authController.fingerprintSensorLocation
- faceSensorLocation = authController.faceSensorLocation
+ faceSensorLocation = facePropertyRepository.sensorLocation.value
}
private fun updateRippleColor() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index b0143f5cdc4a..aaccbc1d2f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -22,6 +22,7 @@ import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManager.DisplayListener
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.os.Handler
+import android.util.Size
import android.view.DisplayInfo
import com.android.internal.util.ArrayUtils
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -40,6 +41,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Repository for the current state of the display */
@@ -58,6 +60,9 @@ interface DisplayStateRepository {
/** Provides the current display rotation */
val currentRotation: StateFlow<DisplayRotation>
+
+ /** Provides the current display size */
+ val currentDisplaySize: StateFlow<Size>
}
// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
@@ -110,17 +115,13 @@ constructor(
initialValue = false,
)
- private fun getDisplayRotation(): DisplayRotation {
+ private fun getDisplayInfo(): DisplayInfo {
val cachedDisplayInfo = DisplayInfo()
context.display?.getDisplayInfo(cachedDisplayInfo)
- var rotation = cachedDisplayInfo.rotation
- if (isReverseDefaultRotation) {
- rotation = (rotation + 1) % 4
- }
- return rotation.toDisplayRotation()
+ return cachedDisplayInfo
}
- override val currentRotation: StateFlow<DisplayRotation> =
+ private val currentDisplayInfo: StateFlow<DisplayInfo> =
conflatedCallbackFlow {
val callback =
object : DisplayListener {
@@ -129,11 +130,11 @@ constructor(
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
- val rotation = getDisplayRotation()
+ val displayInfo = getDisplayInfo()
trySendWithFailureLogging(
- rotation,
+ displayInfo,
TAG,
- "Error sending display rotation to $rotation"
+ "Error sending displayInfo to $displayInfo"
)
}
}
@@ -148,7 +149,37 @@ constructor(
.stateIn(
applicationScope,
started = SharingStarted.Eagerly,
- initialValue = getDisplayRotation(),
+ initialValue = getDisplayInfo(),
+ )
+
+ private fun rotationToDisplayRotation(rotation: Int): DisplayRotation {
+ var adjustedRotation = rotation
+ if (isReverseDefaultRotation) {
+ adjustedRotation = (rotation + 1) % 4
+ }
+ return adjustedRotation.toDisplayRotation()
+ }
+
+ override val currentRotation: StateFlow<DisplayRotation> =
+ currentDisplayInfo
+ .map { rotationToDisplayRotation(it.rotation) }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
+ )
+
+ override val currentDisplaySize: StateFlow<Size> =
+ currentDisplayInfo
+ .map { Size(it.naturalWidth, it.naturalHeight) }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ Size(
+ currentDisplayInfo.value.naturalWidth,
+ currentDisplayInfo.value.naturalHeight
+ ),
)
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
index 0ae2e1614fba..ae1539ebaf89 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -17,25 +17,39 @@
package com.android.systemui.biometrics.data.repository
+import android.content.Context
+import android.graphics.Point
+import android.hardware.camera2.CameraManager
import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
import android.util.Log
+import android.util.RotationUtils
+import android.util.Size
+import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toLockoutMode
+import com.android.systemui.biometrics.shared.model.toRotation
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
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.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -47,20 +61,38 @@ interface FacePropertyRepository {
/** Get the current lockout mode for the user. This makes a binder based service call. */
suspend fun getLockoutMode(userId: Int): LockoutMode
+
+ /** The current face sensor location in current device rotation */
+ val sensorLocation: StateFlow<Point?>
}
/** Describes a biometric sensor */
data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
+/** Data class for camera info */
+private data class CameraInfo(
+ /** The logical id of the camera */
+ val cameraId: String,
+ /** The physical id of the camera */
+ val cameraPhysicalId: String?,
+ /** The center point of the camera in natural orientation */
+ val cameraLocation: Point?,
+)
+
private const val TAG = "FaceSensorPropertyRepositoryImpl"
@SysUISingleton
class FacePropertyRepositoryImpl
@Inject
constructor(
+ @Application val applicationContext: Context,
+ @Main mainExecutor: Executor,
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val faceManager: FaceManager?,
+ private val cameraManager: CameraManager,
+ displayStateRepository: DisplayStateRepository,
+ configurationRepository: ConfigurationRepository,
) : FacePropertyRepository {
override val sensorInfo: StateFlow<FaceSensorInfo?> =
@@ -89,10 +121,179 @@ constructor(
.onEach { Log.d(TAG, "sensorProps changed: $it") }
.stateIn(applicationScope, SharingStarted.Eagerly, null)
+ private val cameraInfoList: List<CameraInfo> = loadCameraInfoList()
+ private var currentPhysicalCameraId: String? = null
+
+ private val defaultSensorLocation: StateFlow<Point?> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : CameraManager.AvailabilityCallback() {
+
+ // This callback will only be called when there is more than one front
+ // camera on the device (e.g. foldable device with cameras on both outer &
+ // inner display).
+ override fun onPhysicalCameraAvailable(
+ cameraId: String,
+ physicalCameraId: String
+ ) {
+ currentPhysicalCameraId = physicalCameraId
+ val cameraInfo =
+ cameraInfoList.firstOrNull {
+ physicalCameraId == it.cameraPhysicalId
+ }
+ trySendWithFailureLogging(
+ cameraInfo?.cameraLocation,
+ TAG,
+ "Update face sensor location to $cameraInfo."
+ )
+ }
+
+ // This callback will only be called when there is more than one front
+ // camera on the device (e.g. foldable device with cameras on both outer &
+ // inner display).
+ //
+ // By default, all cameras are available which means there will be no
+ // onPhysicalCameraAvailable() invoked and depending on the device state
+ // (Fold or unfold), only the onPhysicalCameraUnavailable() for another
+ // camera will be invoke. So we need to use this method to decide the
+ // initial physical ID for foldable devices.
+ override fun onPhysicalCameraUnavailable(
+ cameraId: String,
+ physicalCameraId: String
+ ) {
+ if (currentPhysicalCameraId == null) {
+ val cameraInfo =
+ cameraInfoList.firstOrNull {
+ physicalCameraId != it.cameraPhysicalId
+ }
+ currentPhysicalCameraId = cameraInfo?.cameraPhysicalId
+ trySendWithFailureLogging(
+ cameraInfo?.cameraLocation,
+ TAG,
+ "Update face sensor location to $cameraInfo."
+ )
+ }
+ }
+ }
+ cameraManager.registerAvailabilityCallback(mainExecutor, callback)
+ awaitClose { cameraManager.unregisterAvailabilityCallback(callback) }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue =
+ if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null
+ )
+
+ override val sensorLocation: StateFlow<Point?> =
+ sensorInfo
+ .flatMapLatest { info ->
+ if (info == null) {
+ flowOf(null)
+ } else {
+ combine(
+ defaultSensorLocation,
+ displayStateRepository.currentRotation,
+ displayStateRepository.currentDisplaySize,
+ configurationRepository.scaleForResolution
+ ) { defaultLocation, displayRotation, displaySize, scaleForResolution ->
+ computeCurrentFaceLocation(
+ defaultLocation,
+ displayRotation,
+ displaySize,
+ scaleForResolution
+ )
+ }
+ }
+ }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
+
+ private fun computeCurrentFaceLocation(
+ defaultLocation: Point?,
+ rotation: DisplayRotation,
+ displaySize: Size,
+ scaleForResolution: Float,
+ ): Point? {
+ if (defaultLocation == null) {
+ return null
+ }
+
+ return rotateToCurrentOrientation(
+ Point(
+ (defaultLocation.x * scaleForResolution).toInt(),
+ (defaultLocation.y * scaleForResolution).toInt()
+ ),
+ rotation,
+ displaySize
+ )
+ }
+
+ private fun rotateToCurrentOrientation(
+ inOutPoint: Point,
+ rotation: DisplayRotation,
+ displaySize: Size
+ ): Point {
+ RotationUtils.rotatePoint(
+ inOutPoint,
+ rotation.toRotation(),
+ displaySize.width,
+ displaySize.height
+ )
+ return inOutPoint
+ }
override suspend fun getLockoutMode(userId: Int): LockoutMode {
if (sensorInfo.value == null || faceManager == null) {
return LockoutMode.NONE
}
return faceManager.getLockoutModeForUser(sensorInfo.value!!.id, userId).toLockoutMode()
}
+
+ private fun loadCameraInfoList(): List<CameraInfo> {
+ val list = mutableListOf<CameraInfo>()
+
+ val outer =
+ loadCameraInfo(
+ R.string.config_protectedCameraId,
+ R.string.config_protectedPhysicalCameraId,
+ R.array.config_face_auth_props
+ )
+ if (outer != null) {
+ list.add(outer)
+ }
+
+ val inner =
+ loadCameraInfo(
+ R.string.config_protectedInnerCameraId,
+ R.string.config_protectedInnerPhysicalCameraId,
+ R.array.config_inner_face_auth_props
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraInfo(
+ cameraIdRes: Int,
+ cameraPhysicalIdRes: Int,
+ cameraLocationRes: Int
+ ): CameraInfo? {
+ val cameraId = applicationContext.getString(cameraIdRes)
+ if (cameraId.isNullOrEmpty()) {
+ return null
+ }
+ val physicalCameraId = applicationContext.getString(cameraPhysicalIdRes)
+ val cameraLocation: IntArray = applicationContext.resources.getIntArray(cameraLocationRes)
+ val location: Point?
+ if (cameraLocation.size < 2) {
+ location = null
+ } else {
+ location = Point(cameraLocation[0], cameraLocation[1])
+ }
+ return CameraInfo(cameraId, physicalCameraId, location)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
new file mode 100644
index 000000000000..cf2e33ce1df5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.model
+
+/** Data model of media on the communal hub. */
+data class CommunalMediaModel(
+ val hasAnyMediaOrRecommendation: Boolean,
+ val createdTimestampMillis: Long = 0L,
+) {
+ companion object {
+ val INACTIVE =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e41c32261c11..e8a561b37d20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
/** Encapsulates the state of smartspace in communal. */
interface CommunalMediaRepository {
- val mediaPlaying: Flow<Boolean>
+ val mediaModel: Flow<CommunalMediaModel>
}
@SysUISingleton
@@ -47,27 +46,32 @@ constructor(
receivedSmartspaceCardLatency: Int,
isSsReactivated: Boolean
) {
- if (!mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = true
+ updateMediaModel(data)
}
override fun onMediaDataRemoved(key: String) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = false
+ updateMediaModel()
}
}
- private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ init {
+ mediaDataManager.addListener(mediaDataListener)
+ }
- override val mediaPlaying: Flow<Boolean> =
- _mediaPlaying
- .onStart {
- mediaDataManager.addListener(mediaDataListener)
- _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation()
- }
- .onCompletion { mediaDataManager.removeListener(mediaDataListener) }
+ private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
+ MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ private fun updateMediaModel(data: MediaData? = null) {
+ if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
+ )
+ } else {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be4060223..553b3ebc0813 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -56,6 +56,9 @@ interface CommunalRepository {
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableCommunalTransitionState>
+ /** Whether the CTA tile is visible in the hub under view mode. */
+ val isCtaTileInViewModeVisible: Flow<Boolean>
+
/** Updates the requested scene. */
fun setDesiredScene(desiredScene: CommunalSceneKey)
@@ -65,6 +68,9 @@ interface CommunalRepository {
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
+
+ /** Updates whether to display the CTA tile in the hub under view mode. */
+ fun setCtaTileInViewModeVisibility(isVisible: Boolean)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -96,6 +102,16 @@ constructor(
initialValue = defaultTransitionState,
)
+ // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
+ // once dismissed.
+ private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isCtaTileInViewModeVisible: Flow<Boolean> =
+ _isCtaTileInViewModeVisible.asStateFlow()
+
+ override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+ _isCtaTileInViewModeVisible.value = isVisible
+ }
+
override fun setDesiredScene(desiredScene: CommunalSceneKey) {
_desiredScene.value = desiredScene
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index cab8adfc0bd9..e6816e954b5d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -18,14 +18,12 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
-import android.content.BroadcastReceiver
import android.content.ComponentName
-import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserManager
+import androidx.annotation.WorkerThread
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
@@ -40,17 +38,21 @@ import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
+import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
@@ -58,7 +60,11 @@ interface CommunalWidgetRepository {
val communalWidgets: Flow<List<CommunalWidgetContentModel>>
/** Add a widget at the specified position in the app widget service and the database. */
- fun addWidget(provider: ComponentName, priority: Int) {}
+ fun addWidget(
+ provider: ComponentName,
+ priority: Int,
+ configureWidget: suspend (id: Int) -> Boolean
+ ) {}
/** Delete a widget by id from app widget service and the database. */
fun deleteWidget(widgetId: Int) {}
@@ -97,37 +103,22 @@ constructor(
// Whether the [AppWidgetHost] is listening for updates.
private var isHostListening = false
- private val isUserUnlocked: Flow<Boolean> =
- callbackFlow {
- if (!communalRepository.isCommunalEnabled) {
- awaitClose()
- }
+ private suspend fun isUserUnlockingOrUnlocked(): Boolean =
+ withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
- fun isUserUnlockingOrUnlocked(): Boolean {
- return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
- }
-
- fun send() {
- trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
- }
-
- if (isUserUnlockingOrUnlocked()) {
- send()
- awaitClose()
+ private val isUserUnlocked: Flow<Boolean> =
+ flowOf(communalRepository.isCommunalEnabled)
+ .flatMapLatest { enabled ->
+ if (enabled) {
+ broadcastDispatcher
+ .broadcastFlow(
+ filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ user = userTracker.userHandle
+ )
+ .onStart { emit(Unit) }
+ .mapLatest { isUserUnlockingOrUnlocked() }
} else {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- send()
- }
- }
-
- broadcastDispatcher.registerReceiver(
- receiver,
- IntentFilter(Intent.ACTION_USER_UNLOCKED),
- )
-
- awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ emptyFlow()
}
}
.distinctUntilChanged()
@@ -148,18 +139,52 @@ constructor(
if (!isHostActive || !appWidgetManager.isPresent) {
return@flatMapLatest flowOf(emptyList())
}
- communalWidgetDao.getWidgets().map { it.map(::mapToContentModel) }
+ communalWidgetDao
+ .getWidgets()
+ .map { it.map(::mapToContentModel) }
+ // As this reads from a database and triggers IPCs to AppWidgetManager,
+ // it should be executed in the background.
+ .flowOn(bgDispatcher)
}
- override fun addWidget(provider: ComponentName, priority: Int) {
+ override fun addWidget(
+ provider: ComponentName,
+ priority: Int,
+ configureWidget: suspend (id: Int) -> Boolean
+ ) {
applicationScope.launch(bgDispatcher) {
val id = communalWidgetHost.allocateIdAndBindWidget(provider)
- id?.let {
- communalWidgetDao.addWidget(
- widgetId = it,
- provider = provider,
- priority = priority,
- )
+ if (id != null) {
+ val configured =
+ if (communalWidgetHost.requiresConfiguration(id)) {
+ logger.i("Widget ${provider.flattenToString()} requires configuration.")
+ try {
+ configureWidget.invoke(id)
+ } catch (ex: Exception) {
+ // Cleanup the app widget id if an error happens during configuration.
+ logger.e("Error during widget configuration, cleaning up id $id", ex)
+ if (ex is CancellationException) {
+ appWidgetHost.deleteAppWidgetId(id)
+ // Re-throw cancellation to ensure the parent coroutine also gets
+ // cancelled.
+ throw ex
+ } else {
+ false
+ }
+ }
+ } else {
+ logger.i("Skipping configuration for ${provider.flattenToString()}")
+ true
+ }
+ if (configured) {
+ communalWidgetDao.addWidget(
+ widgetId = id,
+ provider = provider,
+ priority = priority,
+ )
+ } else {
+ appWidgetHost.deleteAppWidgetId(id)
+ }
}
logger.i("Added widget ${provider.flattenToString()} at position $priority.")
}
@@ -182,6 +207,7 @@ constructor(
}
}
+ @WorkerThread
private fun mapToContentModel(
entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
): CommunalWidgetContentModel {
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 18fb895f4aaf..24d4c6c4c397 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
@@ -34,15 +34,13 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Encapsulates business-logic related to communal mode. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
@@ -98,9 +96,20 @@ constructor(
editWidgetsActivityStarter.startActivity()
}
- /** Add a widget at the specified position. */
- fun addWidget(componentName: ComponentName, priority: Int) =
- widgetRepository.addWidget(componentName, priority)
+ /** Dismiss the CTA tile from the hub in view mode. */
+ fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+
+ /**
+ * Add a widget at the specified position.
+ *
+ * @param configureWidget The callback to trigger if widget configuration is needed. Should
+ * return whether configuration was successful.
+ */
+ fun addWidget(
+ componentName: ComponentName,
+ priority: Int,
+ configureWidget: suspend (id: Int) -> Boolean
+ ) = widgetRepository.addWidget(componentName, priority, configureWidget)
/** Delete a widget by id. */
fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
@@ -125,27 +134,25 @@ constructor(
}
}
- /** A flow of available smartspace content. Currently only showing timer targets. */
- val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+ /** A flow of available smartspace targets. Currently only showing timers. */
+ private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
flowOf(emptyList())
} else {
smartspaceRepository.communalSmartspaceTargets.map { targets ->
- targets
- .filter { target ->
- target.featureType == SmartspaceTarget.FEATURE_TIMER &&
- target.remoteViews != null
- }
- .mapIndexed Target@{ index, target ->
- return@Target CommunalContentModel.Smartspace(
- smartspaceTargetId = target.smartspaceTargetId,
- remoteViews = target.remoteViews!!,
- size = dynamicContentSize(targets.size, index),
- )
- }
+ targets.filter { target ->
+ target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+ target.remoteViews != null
+ }
}
}
+ /** CTA tile to be displayed in the glanceable hub (view mode). */
+ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
+ communalRepository.isCtaTileInViewModeVisible.map { visible ->
+ if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+ }
+
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
val tutorialContent: List<CommunalContentModel.Tutorial> =
listOf(
@@ -159,14 +166,43 @@ constructor(
CommunalContentModel.Tutorial(id = 7, HALF),
)
- val umoContent: Flow<List<CommunalContentModel.Umo>> =
- mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
- if (mediaPlaying) {
- // TODO(b/310254801): support HALF and FULL layouts
- flowOf(listOf(CommunalContentModel.Umo(THIRD)))
- } else {
- flowOf(emptyList())
+ /**
+ * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
+ * sized dynamically.
+ */
+ val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
+ combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+ val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
+
+ // Add smartspace
+ ongoingContent.addAll(
+ smartspace.map { target ->
+ CommunalContentModel.Smartspace(
+ smartspaceTargetId = target.smartspaceTargetId,
+ remoteViews = target.remoteViews!!,
+ createdTimestampMillis = target.creationTimeMillis,
+ )
+ }
+ )
+
+ // Add UMO
+ if (media.hasAnyMediaOrRecommendation) {
+ ongoingContent.add(
+ CommunalContentModel.Umo(
+ createdTimestampMillis = media.createdTimestampMillis,
+ )
+ )
}
+
+ // Order by creation time descending
+ ongoingContent.sortByDescending { it.createdTimestampMillis }
+
+ // Dynamic sizing
+ ongoingContent.forEachIndexed { index, model ->
+ model.size = dynamicContentSize(ongoingContent.size, index)
+ }
+
+ return@combine ongoingContent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 3ae522970365..46f957f3aaf2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -30,46 +30,95 @@ sealed interface CommunalContentModel {
/** Size to be rendered in the grid. */
val size: CommunalContentSize
+ /**
+ * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
+ * dynamically.
+ */
+ sealed interface Ongoing : CommunalContentModel {
+ override var size: CommunalContentSize
+
+ /** Timestamp in milliseconds of when the content was created. */
+ val createdTimestampMillis: Long
+ }
+
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: AppWidgetHost,
) : CommunalContentModel {
- override val key = "widget_$appWidgetId"
+ override val key = KEY.widget(appWidgetId)
// Widget size is always half.
override val size = CommunalContentSize.HALF
}
/** A placeholder item representing a new widget being added */
class WidgetPlaceholder : CommunalContentModel {
- override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ override val key: String = KEY.widgetPlaceholder()
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
+ /** A CTA tile in the glanceable hub view mode which can be dismissed. */
+ class CtaTileInViewMode : CommunalContentModel {
+ override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
+ /** A CTA tile in the glanceable hub edit model which remains visible in the grid. */
+ class CtaTileInEditMode : CommunalContentModel {
+ override val key: String = KEY.CTA_TILE_IN_EDIT_MODE_KEY
// Same as widget size.
override val size = CommunalContentSize.HALF
}
class Tutorial(
id: Int,
- override val size: CommunalContentSize,
+ override var size: CommunalContentSize,
) : CommunalContentModel {
- override val key = "tutorial_$id"
+ override val key = KEY.tutorial(id)
}
class Smartspace(
smartspaceTargetId: String,
val remoteViews: RemoteViews,
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = "smartspace_$smartspaceTargetId"
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.smartspace(smartspaceTargetId)
}
class Umo(
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = UMO_KEY
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.umo()
}
- companion object {
- /** Key for the [Umo] in CommunalContentModel. There should only ever be one UMO. */
- const val UMO_KEY = "umo"
+ class KEY {
+ companion object {
+ const val CTA_TILE_IN_VIEW_MODE_KEY = "cta_tile_in_view_mode"
+ const val CTA_TILE_IN_EDIT_MODE_KEY = "cta_tile_in_edit_mode"
+
+ fun widget(id: Int): String {
+ return "widget_$id"
+ }
+
+ fun widgetPlaceholder(): String {
+ return "widget_placeholder_${UUID.randomUUID()}"
+ }
+
+ fun tutorial(id: Int): String {
+ return "tutorial_$id"
+ }
+
+ fun smartspace(id: String): String {
+ return "smartspace_$id"
+ }
+
+ fun umo(): String {
+ return "umo"
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 155de323d3a6..41f9cb4c98ed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -18,6 +18,8 @@ package com.android.systemui.communal.shared
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
+import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -63,4 +65,23 @@ constructor(
}
return false
}
+
+ /**
+ * Returns whether a particular widget requires configuration when it is first added.
+ *
+ * Must be called after the widget id has been bound.
+ */
+ fun requiresConfiguration(widgetId: Int): Boolean {
+ if (appWidgetManager.isPresent) {
+ val widgetInfo = appWidgetManager.get().getAppWidgetInfo(widgetId)
+ val featureFlags: Int = widgetInfo.widgetFeatures
+ // A widget's configuration is optional only if it's configuration is marked as optional
+ // AND it can be reconfigured later.
+ val configurationOptional =
+ (featureFlags and WIDGET_FEATURE_CONFIGURATION_OPTIONAL != 0 &&
+ featureFlags and WIDGET_FEATURE_RECONFIGURABLE != 0)
+ return widgetInfo.configure != null && !configurationOptional
+ }
+ return false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
new file mode 100644
index 000000000000..e167f3e263fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.communal.shared.log
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+
+/** UI events for the Communal Hub. */
+enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
+ @UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
+ @UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
+ @UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
+ @UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
+ @UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
+ @UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
+ COMMUNAL_HUB_LOADED(1579),
+ @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_START(1580),
+ @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH(1581),
+ @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL(1582),
+ @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_START(1583),
+ @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH(1584),
+ @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub")
+ COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL(1585),
+ @UiEvent(doc = "User starts the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_START(1586),
+ @UiEvent(doc = "User finishes the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_FINISH(1587),
+ @UiEvent(doc = "User cancels the drag gesture to reorder a widget")
+ COMMUNAL_HUB_REORDER_WIDGET_CANCEL(1588),
+ @UiEvent(doc = "Edit mode for the Communal Hub is shown") COMMUNAL_HUB_EDIT_MODE_SHOWN(1569),
+ @UiEvent(doc = "Edit mode for the Communal Hub is gone") COMMUNAL_HUB_EDIT_MODE_GONE(1589),
+ @UiEvent(doc = "Widget picker for the Communal Hub is shown")
+ COMMUNAL_HUB_WIDGET_PICKER_SHOWN(1590),
+ @UiEvent(doc = "Widget picker for the Communal Hub is gone")
+ COMMUNAL_HUB_WIDGET_PICKER_GONE(1591),
+ @UiEvent(doc = "User performs a swipe up gesture from bottom to enter bouncer")
+ COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
+ @UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
+ COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574);
+
+ override fun getId(): Int {
+ return id
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index c34a8df1bed7..97e530ace9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -58,8 +58,16 @@ abstract class BaseCommunalViewModel(
/**
* Called when a widget is added via drag and drop from the widget picker into the communal hub.
*/
- fun onAddWidget(componentName: ComponentName, priority: Int) {
- communalInteractor.addWidget(componentName, priority)
+ open fun onAddWidget(componentName: ComponentName, priority: Int) {
+ communalInteractor.addWidget(componentName, priority, ::configureWidget)
+ }
+
+ /**
+ * Called when a widget needs to be configured, with the id of the widget. The return value
+ * should represent whether configuring the widget was successful.
+ */
+ protected open suspend fun configureWidget(widgetId: Int): Boolean {
+ return true
}
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
@@ -103,6 +111,9 @@ abstract class BaseCommunalViewModel(
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
+ /** Called as the UI requests to dismiss the CTA tile. */
+ open fun onDismissCtaTile() {}
+
/** Gets the interaction handler used to handle taps on a remote view */
abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index da7bd34950df..a03e6c1aee97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -16,6 +16,13 @@
package com.android.systemui.communal.ui.viewmodel
+import android.app.Activity
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
+import android.app.ActivityOptions
+import android.appwidget.AppWidgetHost
+import android.content.ActivityNotFoundException
+import android.content.ComponentName
import android.os.PowerManager
import android.widget.RemoteViews
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,10 +31,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.util.nullableAtomicReference
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.map
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -35,16 +46,34 @@ class CommunalEditModeViewModel
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
+ private val appWidgetHost: AppWidgetHost,
shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
+ private companion object {
+ private const val KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"
+ private const val SPLASH_SCREEN_STYLE_EMPTY = 0
+ }
+
+ private val _widgetsToConfigure = MutableSharedFlow<Int>()
+
+ /**
+ * Flow emitting ids of widgets which need to be configured. The consumer of this flow should
+ * trigger [startConfigurationActivity] to initiate configuration.
+ */
+ val widgetsToConfigure: Flow<Int> = _widgetsToConfigure
+
+ private var pendingConfiguration: CompletableDeferred<Int>? by nullableAtomicReference()
+
override val isEditMode = true
- // Only widgets are editable.
+ // Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent
+ communalInteractor.widgetContent.map { widgets ->
+ widgets + listOf(CommunalContentModel.CtaTileInEditMode())
+ }
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
@@ -55,4 +84,55 @@ constructor(
// Ignore all interactions in edit mode.
return RemoteViews.InteractionHandler { _, _, _ -> false }
}
+
+ override fun onAddWidget(componentName: ComponentName, priority: Int) {
+ if (pendingConfiguration != null) {
+ throw IllegalStateException(
+ "Cannot add $componentName widget while widget configuration is pending"
+ )
+ }
+ super.onAddWidget(componentName, priority)
+ }
+
+ fun startConfigurationActivity(activity: Activity, widgetId: Int, requestCode: Int) {
+ val options =
+ ActivityOptions.makeBasic().apply {
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ }
+ val bundle = options.toBundle()
+ bundle.putInt(KEY_SPLASH_SCREEN_STYLE, SPLASH_SCREEN_STYLE_EMPTY)
+ try {
+ appWidgetHost.startAppWidgetConfigureActivityForResult(
+ activity,
+ widgetId,
+ 0,
+ // Use the widget id as the request code.
+ requestCode,
+ bundle
+ )
+ } catch (e: ActivityNotFoundException) {
+ setConfigurationResult(RESULT_CANCELED)
+ }
+ }
+
+ override suspend fun configureWidget(widgetId: Int): Boolean {
+ if (pendingConfiguration != null) {
+ throw IllegalStateException(
+ "Attempting to configure $widgetId while another configuration is already active"
+ )
+ }
+ pendingConfiguration = CompletableDeferred()
+ _widgetsToConfigure.emit(widgetId)
+ val resultCode = pendingConfiguration?.await() ?: RESULT_CANCELED
+ pendingConfiguration = null
+ return resultCode == RESULT_OK
+ }
+
+ /** Sets the result of widget configuration. */
+ fun setConfigurationResult(resultCode: Int) {
+ pendingConfiguration?.complete(resultCode)
+ ?: throw IllegalStateException("No widget pending configuration")
+ }
}
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 2fae8b533857..066e897cdfdb 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
@@ -54,15 +54,18 @@ constructor(
return@flatMapLatest flowOf(communalInteractor.tutorialContent)
}
combine(
- communalInteractor.smartspaceContent,
- communalInteractor.umoContent,
+ communalInteractor.ongoingContent,
communalInteractor.widgetContent,
- ) { smartspace, umo, widgets ->
- smartspace + umo + widgets
+ communalInteractor.ctaTileContent,
+ ) { ongoing, widgets, ctaTile,
+ ->
+ ongoing + widgets + ctaTile
}
}
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+ override fun onDismissCtaTile() = communalInteractor.dismissCtaTile()
+
override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 0f94a92dd7ce..bfc6f2b14acd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -27,23 +27,26 @@ import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.compose.ComposeFacade.setCommunalEditWidgetActivityContent
import javax.inject.Inject
+import kotlinx.coroutines.launch
/** An Activity for editing the widgets that appear in hub mode. */
class EditWidgetsActivity
@Inject
constructor(
private val communalViewModel: CommunalEditModeViewModel,
- private val communalInteractor: CommunalInteractor,
private var windowManagerService: IWindowManager? = null,
) : ComponentActivity() {
companion object {
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
+ private const val REQUEST_CODE_CONFIGURE_WIDGET = 1
private const val TAG = "EditWidgetsActivity"
}
@@ -63,7 +66,7 @@ constructor(
Intent.EXTRA_COMPONENT_NAME,
ComponentName::class.java
)
- ?.let { communalInteractor.addWidget(it, 0) }
+ ?.let { communalViewModel.onAddWidget(it, 0) }
?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
}
}
@@ -84,14 +87,26 @@ constructor(
windowInsetsController?.hide(WindowInsets.Type.systemBars())
window.setDecorFitsSystemWindows(false)
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Start the configuration activity when new widgets are added.
+ communalViewModel.widgetsToConfigure.collect { widgetId ->
+ communalViewModel.startConfigurationActivity(
+ activity = this@EditWidgetsActivity,
+ widgetId = widgetId,
+ requestCode = REQUEST_CODE_CONFIGURE_WIDGET
+ )
+ }
+ }
+ }
+
setCommunalEditWidgetActivityContent(
activity = this,
viewModel = communalViewModel,
onOpenWidgetPicker = {
- val localPackageManager: PackageManager = getPackageManager()
val intent =
Intent(Intent.ACTION_MAIN).also { it.addCategory(Intent.CATEGORY_HOME) }
- localPackageManager
+ packageManager
.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
?.activityInfo
?.packageName
@@ -122,4 +137,11 @@ constructor(
}
)
}
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == REQUEST_CODE_CONFIGURE_WIDGET) {
+ communalViewModel.setConfigurationResult(resultCode)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8b992fcae67e..b2d70523c282 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -91,6 +91,7 @@ import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.satellite.SatelliteManager;
import android.view.Choreographer;
import android.view.CrossWindowBlurListeners;
import android.view.IWindowManager;
@@ -712,4 +713,10 @@ public class FrameworkServicesModule {
ServiceManager.getService(Context.URI_GRANTS_SERVICE)
);
}
+
+ @Provides
+ @Singleton
+ static Optional<SatelliteManager> provideSatelliteManager(Context context) {
+ return Optional.ofNullable(context.getSystemService(SatelliteManager.class));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 615b503b9fae..3bc4f342c566 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -32,6 +32,7 @@ import android.widget.FrameLayout
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.ScreenDecorationsLogger
@@ -41,19 +42,20 @@ import javax.inject.Inject
@SysUISingleton
class FaceScanningProviderFactory @Inject constructor(
- private val authController: AuthController,
- private val context: Context,
- private val statusBarStateController: StatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- @Main private val mainExecutor: Executor,
- private val logger: ScreenDecorationsLogger,
+ private val authController: AuthController,
+ private val context: Context,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ @Main private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
+ private val facePropertyRepository: FacePropertyRepository,
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
override val hasProviders: Boolean
get() {
- if (authController.faceSensorLocation == null) {
+ if (facePropertyRepository.sensorLocation.value == null) {
return false
}
@@ -86,6 +88,7 @@ class FaceScanningProviderFactory @Inject constructor(
keyguardUpdateMonitor,
mainExecutor,
logger,
+ facePropertyRepository,
)
)
}
@@ -104,12 +107,13 @@ class FaceScanningProviderFactory @Inject constructor(
}
class FaceScanningOverlayProviderImpl(
- override val alignedBound: Int,
- private val authController: AuthController,
- private val statusBarStateController: StatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val mainExecutor: Executor,
- private val logger: ScreenDecorationsLogger,
+ override val alignedBound: Int,
+ private val authController: AuthController,
+ private val statusBarStateController: StatusBarStateController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mainExecutor: Executor,
+ private val logger: ScreenDecorationsLogger,
+ private val facePropertyRepository: FacePropertyRepository,
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.res.R.id.face_scanning_anim
@@ -162,8 +166,9 @@ class FaceScanningOverlayProviderImpl(
layoutParams.let { lp ->
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
lp.height = ViewGroup.LayoutParams.MATCH_PARENT
- logger.faceSensorLocation(authController.faceSensorLocation)
- authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
+ logger.faceSensorLocation(facePropertyRepository.sensorLocation.value)
+ facePropertyRepository.sensorLocation.value?.y?.let {
+ faceAuthSensorHeight ->
val faceScanningHeight = (faceAuthSensorHeight * 2)
when (rotation) {
Surface.ROTATION_0, Surface.ROTATION_180 ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 38c7c6ac67cb..699532cbfca3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -448,11 +448,6 @@ object Flags {
// TODO(b/270987164): Tracking Bug
@JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
- // TODO(b/265639042): Tracking Bug
- @JvmField
- val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
- unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
-
// TODO(b/273800936): Tracking Bug
@JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
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 2f937bcd3414..704ebdd40af6 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
@@ -21,6 +21,7 @@ import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
@@ -277,6 +278,7 @@ constructor(
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
+ facePropertyRepository: FacePropertyRepository,
) : KeyguardRepository {
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
@@ -599,27 +601,7 @@ constructor(
awaitClose { authController.removeCallback(callback) }
}
- override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
- fun sendSensorLocation() {
- trySendWithFailureLogging(
- authController.faceSensorLocation,
- TAG,
- "AuthController.Callback#onFingerprintLocationChanged"
- )
- }
-
- val callback =
- object : AuthController.Callback {
- override fun onFaceSensorLocationChanged() {
- sendSensorLocation()
- }
- }
-
- authController.addCallback(callback)
- sendSensorLocation()
-
- awaitClose { authController.removeCallback(callback) }
- }
+ override val faceSensorLocation: Flow<Point?> = facePropertyRepository.sensorLocation
override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index b98e9c232d58..5caa27f02bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -81,9 +81,12 @@ data class MediaData(
/** Set from the notification and used as fallback when PlaybackState cannot be determined */
val isClearable: Boolean = true,
- /** Timestamp when this player was last active. */
+ /** Milliseconds since boot when this player was last active. */
var lastActive: Long = 0L,
+ /** Timestamp in milliseconds when this player was created. */
+ var createdTimestampMillis: Long = 0L,
+
/** Instance ID for logging purposes */
val instanceId: InstanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3e8b49d4a653..47df3b79b8bb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -400,7 +400,12 @@ class MediaDataManager(
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
- val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
+ val temp =
+ LOADING.copy(
+ packageName = sbn.packageName,
+ instanceId = instanceId,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
+ )
mediaEntries.put(key, temp)
isNewlyActiveEntry = true
} else if (oldKey != key) {
@@ -454,7 +459,8 @@ class MediaDataManager(
resumeAction = action,
hasCheckedForResume = true,
instanceId = instanceId,
- appUid = appUid
+ appUid = appUid,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
)
mediaEntries.put(packageName, resumeData)
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
@@ -732,6 +738,7 @@ class MediaDataManager(
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
onMediaDataLoaded(
packageName,
@@ -757,6 +764,7 @@ class MediaDataManager(
notificationKey = packageName,
hasCheckedForResume = true,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
@@ -907,6 +915,7 @@ class MediaDataManager(
}
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -937,6 +946,7 @@ class MediaDataManager(
isPlaying = isPlaying,
isClearable = !sbn.isOngoing,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 6ec46f627264..df6843d31ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -61,6 +61,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.res.R;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.system.QuickStepContract;
public class KeyButtonView extends ImageView implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ddd7d6781c46..51b94dd983f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -189,6 +189,7 @@ public class QSPanel extends LinearLayout implements Tunable {
public void setBrightnessView(@NonNull View view) {
if (mBrightnessView != null) {
removeView(mBrightnessView);
+ mChildrenLayoutTop.remove(mBrightnessView);
mMovableContentStartIndex--;
}
addView(view, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 5eb9620d7334..ef58a608aa1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -56,14 +56,18 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
private final QSCustomizerController mQsCustomizerController;
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
private final FalsingManager mFalsingManager;
- private final BrightnessController mBrightnessController;
- private final BrightnessSliderController mBrightnessSliderController;
- private final BrightnessMirrorHandler mBrightnessMirrorHandler;
+ private BrightnessController mBrightnessController;
+ private BrightnessSliderController mBrightnessSliderController;
+ private BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mListening;
private final boolean mSceneContainerEnabled;
+ private int mLastDensity;
+ private final BrightnessSliderController.Factory mBrightnessSliderControllerFactory;
+ private final BrightnessController.Factory mBrightnessControllerFactory;
+
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -93,6 +97,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
mFalsingManager = falsingManager;
+ mBrightnessSliderControllerFactory = brightnessSliderFactory;
+ mBrightnessControllerFactory = brightnessControllerFactory;
mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
mView.setBrightnessView(mBrightnessSliderController.getRootView());
@@ -100,6 +106,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLastDensity = view.getResources().getConfiguration().densityDpi;
mSceneContainerEnabled = sceneContainerFlags.isEnabled();
}
@@ -147,11 +154,31 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Override
protected void onConfigurationChanged() {
mView.updateResources();
+ int newDensity = mView.getResources().getConfiguration().densityDpi;
+ if (newDensity != mLastDensity) {
+ mLastDensity = newDensity;
+ reinflateBrightnessSlider();
+ }
+
if (mView.isListening()) {
refreshAllTiles();
}
}
+ private void reinflateBrightnessSlider() {
+ mBrightnessController.unregisterCallbacks();
+ mBrightnessSliderController =
+ mBrightnessSliderControllerFactory.create(getContext(), mView);
+ mView.setBrightnessView(mBrightnessSliderController.getRootView());
+ mBrightnessController = mBrightnessControllerFactory.create(mBrightnessSliderController);
+ mBrightnessMirrorHandler.setBrightnessController(mBrightnessController);
+ mBrightnessSliderController.init();
+ if (mListening) {
+ mBrightnessController.registerCallbacks();
+ }
+ }
+
+
@Override
protected void onSplitShadeChanged(boolean shouldUseSplitNotificationShade) {
((PagedTileLayout) mView.getOrCreateTileLayout())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 36dc7433df0a..a01d65896592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -67,9 +67,7 @@ public class ReduceBrightColorsController implements
synchronized (mListeners) {
if (setting != null && mListeners.size() != 0) {
if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
- for (Listener listener : mListeners) {
- listener.onActivated(mManager.isReduceBrightColorsActivated());
- }
+ dispatchOnActivated(mManager.isReduceBrightColorsActivated());
}
}
}
@@ -125,6 +123,13 @@ public class ReduceBrightColorsController implements
mManager.setReduceBrightColorsActivated(activated);
}
+ private void dispatchOnActivated(boolean activated) {
+ ArrayList<Listener> copy = new ArrayList<>(mListeners);
+ for (Listener l : copy) {
+ l.onActivated(activated);
+ }
+ }
+
/**
* Listener invoked whenever the Reduce Bright Colors settings are changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index e075e76595d4..2b8c335cb0ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -24,6 +24,7 @@ import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
import java.time.Instant
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -36,10 +37,12 @@ class AlarmTileMapper
constructor(
@Main private val resources: Resources,
private val theme: Theme,
+ private val clock: SystemClock,
) : QSTileDataToStateMapper<AlarmTileModel> {
companion object {
val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
+ val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
}
override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -47,14 +50,32 @@ constructor(
is AlarmTileModel.NextAlarmSet -> {
activationState = QSTileState.ActivationState.ACTIVE
- val localDateTime =
+ val alarmDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
TimeZone.getDefault().toZoneId()
)
- secondaryLabel =
- if (data.is24HourFormat) formatter24Hour.format(localDateTime)
- else formatter12Hour.format(localDateTime)
+
+ val nowDateTime =
+ LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(clock.currentTimeMillis()),
+ TimeZone.getDefault().toZoneId()
+ )
+
+ // Edge case: If it's 8:00:30 right now and alarm is requested for next week at
+ // 8:00:29, we still want to show the date. Same at nanosecond level.
+ val nextWeekThisTime = nowDateTime.plusWeeks(1).withSecond(0).withNano(0)
+
+ // is the alarm over a week away?
+ val shouldShowDateAndHideTime = alarmDateTime >= nextWeekThisTime
+
+ if (shouldShowDateAndHideTime) {
+ secondaryLabel = formatterDateOnly.format(alarmDateTime)
+ } else {
+ secondaryLabel =
+ if (data.is24HourFormat) formatter24Hour.format(alarmDateTime)
+ else formatter12Hour.format(alarmDateTime)
+ }
}
is AlarmTileModel.NoAlarmSet -> {
activationState = QSTileState.ActivationState.INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
index 51aa339149a4..701d814a843b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -19,9 +19,16 @@ package com.android.systemui.settings.brightness
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
-class BrightnessMirrorHandler(private val brightnessController: MirroredBrightnessController) {
+class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) {
- private var mirrorController: BrightnessMirrorController? = null
+ var mirrorController: BrightnessMirrorController? = null
+ private set
+
+ var brightnessController: MirroredBrightnessController = brightnessController
+ set(value) {
+ field = value
+ updateBrightnessMirror()
+ }
private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
@@ -33,7 +40,7 @@ class BrightnessMirrorHandler(private val brightnessController: MirroredBrightne
mirrorController?.removeCallback(brightnessMirrorListener)
}
- fun setController(controller: BrightnessMirrorController) {
+ fun setController(controller: BrightnessMirrorController?) {
mirrorController?.removeCallback(brightnessMirrorListener)
mirrorController = controller
mirrorController?.addCallback(brightnessMirrorListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index a957095536eb..32cd56ca223f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.dagger;
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
import android.content.Context;
import android.os.RemoteException;
import android.service.dreams.IDreamManager;
@@ -31,8 +33,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.DisplayTracker;
@@ -230,11 +230,11 @@ public interface CentralSurfacesDependenciesModule {
/** */
@Provides
@SysUISingleton
- static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
+ static AnimationFeatureFlags provideAnimationFeatureFlags() {
return new AnimationFeatureFlags() {
@Override
public boolean isPredictiveBackQsDialogAnim() {
- return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+ return predictiveBackAnimateDialogs();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 0c67279c1660..3f2c818399d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -31,6 +31,7 @@ import com.android.systemui.shade.ShadeExpansionListener
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -58,6 +59,7 @@ constructor(
private val dozeParameters: DozeParameters,
private val screenOffAnimationController: ScreenOffAnimationController,
private val logger: NotificationWakeUpCoordinatorLogger,
+ private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
) :
OnHeadsUpChangedListener,
StatusBarStateController.StateListener,
@@ -144,6 +146,7 @@ constructor(
for (listener in wakeUpListeners) {
listener.onFullyHiddenChanged(value)
}
+ notifsKeyguardInteractor.setNotificationsFullyHidden(value)
}
}
@@ -216,6 +219,7 @@ constructor(
for (listener in wakeUpListeners) {
listener.onPulseExpandingChanged(pulseExpanding)
}
+ notifsKeyguardInteractor.setPulseExpanding(pulseExpanding)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 5435fb5449cd..2cac0002f013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -15,8 +15,6 @@
*/
package com.android.systemui.statusbar.notification.data
-import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardStateRepositoryModule
import dagger.Module
-@Module(includes = [NotificationsKeyguardStateRepositoryModule::class])
-interface NotificationDataLayerModule
+@Module(includes = []) interface NotificationDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index 2cc1403a80a5..bd6ea30c44e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -15,59 +15,16 @@
*/
package com.android.systemui.statusbar.notification.data.repository
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
-import dagger.Binds
-import dagger.Module
import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
/** View-states pertaining to notifications on the keyguard. */
-interface NotificationsKeyguardViewStateRepository {
+@SysUISingleton
+class NotificationsKeyguardViewStateRepository @Inject constructor() {
/** Are notifications fully hidden from view? */
- val areNotificationsFullyHidden: Flow<Boolean>
+ val areNotificationsFullyHidden = MutableStateFlow(false)
/** Is a pulse expansion occurring? */
- val isPulseExpanding: Flow<Boolean>
-}
-
-@Module
-interface NotificationsKeyguardStateRepositoryModule {
- @Binds
- fun bindImpl(
- impl: NotificationsKeyguardViewStateRepositoryImpl
- ): NotificationsKeyguardViewStateRepository
-}
-
-@SysUISingleton
-class NotificationsKeyguardViewStateRepositoryImpl
-@Inject
-constructor(
- wakeUpCoordinator: NotificationWakeUpCoordinator,
-) : NotificationsKeyguardViewStateRepository {
- override val areNotificationsFullyHidden: Flow<Boolean> = conflatedCallbackFlow {
- val listener =
- object : NotificationWakeUpCoordinator.WakeUpListener {
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- trySend(isFullyHidden)
- }
- }
- trySend(wakeUpCoordinator.notificationsFullyHidden)
- wakeUpCoordinator.addListener(listener)
- awaitClose { wakeUpCoordinator.removeListener(listener) }
- }
-
- override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
- val listener =
- object : NotificationWakeUpCoordinator.WakeUpListener {
- override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
- trySend(isPulseExpanding)
- }
- }
- trySend(wakeUpCoordinator.isPulseExpanding())
- wakeUpCoordinator.addListener(listener)
- awaitClose { wakeUpCoordinator.removeListener(listener) }
- }
+ val isPulseExpanding = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
index 73341dbc4999..a6361cbc9f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -15,24 +15,29 @@
*/
package com.android.systemui.statusbar.notification.domain.interactor
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOn
/** Domain logic pertaining to notifications on the keyguard. */
class NotificationsKeyguardInteractor
@Inject
constructor(
- repository: NotificationsKeyguardViewStateRepository,
- @Background backgroundDispatcher: CoroutineDispatcher,
+ private val repository: NotificationsKeyguardViewStateRepository,
) {
/** Is a pulse expansion occurring? */
- val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding.flowOn(backgroundDispatcher)
+ val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
/** Are notifications fully hidden from view? */
- val areNotificationsFullyHidden: Flow<Boolean> =
- repository.areNotificationsFullyHidden.flowOn(backgroundDispatcher)
+ val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
+
+ /** Updates whether notifications are fully hidden from view. */
+ fun setNotificationsFullyHidden(fullyHidden: Boolean) {
+ repository.areNotificationsFullyHidden.value = fullyHidden
+ }
+
+ /** Updates whether a pulse expansion is occurring. */
+ fun setPulseExpanding(pulseExpanding: Boolean) {
+ repository.isPulseExpanding.value = pulseExpanding
+ }
}
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 6f5058c8c52e..2e545126c634 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
@@ -366,8 +366,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Override
public void onStatePostChange() {
- mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
- mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
if (!FooterViewRefactor.isEnabled()) {
updateImportantForAccessibility();
@@ -378,7 +377,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
@Override
public void onUserChanged(int userId) {
- mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(false);
mHistoryEnabled = null;
updateFooter();
}
@@ -388,7 +387,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
* Recalculate sensitiveness without animation; called when waking up while keyguard occluded.
*/
public void updateSensitivenessForOccludedWakeup() {
- mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ updateSensitivenessWithAnimation(false);
+ }
+
+ private void updateSensitivenessWithAnimation(boolean animate) {
+ mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index 38a6d39b04e2..13d7924a8be6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -34,7 +34,6 @@ import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.model.SysUiState
/**
@@ -53,7 +52,6 @@ class ComponentSystemUIDialog(
context: Context,
theme: Int,
dismissOnDeviceLock: Boolean,
- featureFlags: FeatureFlags,
dialogManager: SystemUIDialogManager,
sysUiState: SysUiState,
broadcastDispatcher: BroadcastDispatcher,
@@ -63,7 +61,6 @@ class ComponentSystemUIDialog(
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index abdf8277e0c9..56ea00cf0954 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -104,7 +104,8 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
}
private void notifyManagedProfileRemoved() {
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileRemoved();
}
}
@@ -148,7 +149,8 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileChanged();
}
}
@@ -156,7 +158,8 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
@Override
public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
+ ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onManagedProfileChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 9ae41951bb74..d7cbe5df419b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,6 +14,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_BINDABLE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
@@ -40,11 +41,13 @@ import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
+import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
@@ -432,6 +435,10 @@ public interface StatusBarIconController {
case TYPE_MOBILE_NEW:
return addNewMobileIcon(index, slot, holder.getTag());
+
+ case TYPE_BINDABLE:
+ // Safe cast, since only BindableIconHolders can set this tag on themselves
+ return addBindableIcon((BindableIconHolder) holder, index);
}
return null;
@@ -446,6 +453,18 @@ public interface StatusBarIconController {
return view;
}
+ /**
+ * ModernStatusBarViews can be created and bound, and thus do not need to update their
+ * drawable by sending multiple calls to setIcon. Instead, by using a bindable
+ * icon view, we can simply create the icon when requested and allow the
+ * ViewBinder to control its visual state.
+ */
+ protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder, int index) {
+ ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
+ mGroup.addView(view, index, onCreateLayoutParams());
+ return view;
+ }
+
protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
mGroup.addView(view, index, onCreateLayoutParams());
@@ -530,6 +549,7 @@ public interface StatusBarIconController {
return;
case TYPE_MOBILE_NEW:
case TYPE_WIFI_NEW:
+ case TYPE_BINDABLE:
// Nothing, the new icons update themselves
return;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 0f4d68c68d00..4f148f112c52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -38,8 +38,11 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.icons.shared.BindableIconsRegistry;
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
@@ -83,7 +86,8 @@ public class StatusBarIconControllerImpl implements Tunable,
TunerService tunerService,
DumpManager dumpManager,
StatusBarIconList statusBarIconList,
- StatusBarPipelineFlags statusBarPipelineFlags
+ StatusBarPipelineFlags statusBarPipelineFlags,
+ BindableIconsRegistry modernIconsRegistry
) {
mStatusBarIconList = statusBarIconList;
mContext = context;
@@ -94,6 +98,28 @@ public class StatusBarIconControllerImpl implements Tunable,
tunerService.addTunable(this, ICON_HIDE_LIST);
demoModeController.addCallback(this);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
+
+ addModernBindableIcons(modernIconsRegistry);
+ }
+
+ /**
+ * BindableIcons will always produce ModernStatusBarViews, which will be initialized and bound
+ * upon being added to any icon group. Because their view policy does not require subsequent
+ * calls to setIcon(), we can simply register them all statically here and not have to build
+ * CoreStartables for each modern icon.
+ *
+ * @param registry a statically defined provider of the modern icons
+ */
+ private void addModernBindableIcons(BindableIconsRegistry registry) {
+ List<BindableIcon> icons = registry.getBindableIcons();
+
+ // Initialization point for the bindable (modern) icons. These icons get their own slot
+ // allocated immediately, and are required to control their own display properties
+ for (BindableIcon i : icons) {
+ if (i.getShouldBindIcon()) {
+ addBindableIcon(i);
+ }
+ }
}
/** */
@@ -182,6 +208,17 @@ public class StatusBarIconControllerImpl implements Tunable,
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
+ void addBindableIcon(BindableIcon icon) {
+ StatusBarIconHolder existingHolder = mStatusBarIconList.getIconHolder(icon.getSlot(), 0);
+ // Expected to be null
+ if (existingHolder == null) {
+ BindableIconHolder bindableIcon = new BindableIconHolder(icon.getInitializer());
+ setIcon(icon.getSlot(), bindableIcon);
+ } else {
+ Log.e(TAG, "addBindableIcon called, but icon has already been added. Ignoring");
+ }
+ }
+
/** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
index 5b55a1e73dc3..bef0b286ebf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -21,23 +21,24 @@ import android.graphics.drawable.Icon
import android.os.UserHandle
import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
-class StatusBarIconHolder private constructor() {
- @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+open class StatusBarIconHolder private constructor() {
+ @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW, TYPE_BINDABLE)
@Retention(AnnotationRetention.SOURCE)
internal annotation class IconType
var icon: StatusBarIcon? = null
@IconType
- var type = TYPE_ICON
- private set
+ open var type = TYPE_ICON
+ internal set
var tag = 0
private set
- var isVisible: Boolean
+ open var isVisible: Boolean
get() =
when (type) {
TYPE_ICON -> icon!!.visible
@@ -45,6 +46,7 @@ class StatusBarIconHolder private constructor() {
// The new pipeline controls visibilities via the view model and
// view binder, so
// this is effectively an unused return value.
+ TYPE_BINDABLE,
TYPE_MOBILE_NEW,
TYPE_WIFI_NEW -> true
else -> true
@@ -55,6 +57,7 @@ class StatusBarIconHolder private constructor() {
}
when (type) {
TYPE_ICON -> icon!!.visible = visible
+ TYPE_BINDABLE,
TYPE_MOBILE_NEW,
TYPE_WIFI_NEW -> {}
}
@@ -94,6 +97,9 @@ class StatusBarIconHolder private constructor() {
)
const val TYPE_WIFI_NEW = 4
+ /** Only applicable to [BindableIconHolder] */
+ const val TYPE_BINDABLE = 5
+
/** Returns a human-readable string representing the given type. */
fun getTypeString(@IconType type: Int): String {
return when (type) {
@@ -154,4 +160,25 @@ class StatusBarIconHolder private constructor() {
return holder
}
}
+
+ /**
+ * Subclass of StatusBarIconHolder that is responsible only for the registration of an icon into
+ * the [StatusBarIconList]. A bindable icon takes care of its own display, including hiding
+ * itself under the correct conditions.
+ *
+ * StatusBarIconController will register all available bindable icons on init (see
+ * [BindableIconsRepository]), and will ignore any call to setIcon for these.
+ *
+ * [initializer] a view creator that can bind the relevant view models to the created view.
+ */
+ class BindableIconHolder(val initializer: ModernStatusBarViewCreator) : StatusBarIconHolder() {
+ override var type: Int = TYPE_BINDABLE
+
+ /** This is unused, as bindable icons use their own view binders to control visibility */
+ override var isVisible: Boolean = true
+
+ override fun toString(): String {
+ return ("StatusBarIconHolder(type=BINDABLE)")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 3394eacddbd8..390d2c973882 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
@@ -45,8 +47,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.QuickStepContract;
@@ -78,7 +78,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
public static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final DialogDelegate<SystemUIDialog> mDelegate;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
@@ -110,7 +109,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
// SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
// the content and attach listeners.
this(context, theme, dismissOnDeviceLock,
- Dependency.get(FeatureFlags.class),
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
@@ -119,7 +117,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
public static class Factory {
private final Context mContext;
- private final FeatureFlags mFeatureFlags;
private final SystemUIDialogManager mSystemUIDialogManager;
private final SysUiState mSysUiState;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -128,13 +125,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
@Inject
public Factory(
@Application Context context,
- FeatureFlags featureFlags,
SystemUIDialogManager systemUIDialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
- mFeatureFlags = featureFlags;
mSystemUIDialogManager = systemUIDialogManager;
mSysUiState = sysUiState;
mBroadcastDispatcher = broadcastDispatcher;
@@ -177,7 +172,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
context,
DEFAULT_THEME,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mFeatureFlags,
mSystemUIDialogManager,
mSysUiState,
mBroadcastDispatcher,
@@ -190,7 +184,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -199,7 +192,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
@@ -211,7 +203,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -221,7 +212,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
@@ -233,7 +223,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
Context context,
int theme,
boolean dismissOnDeviceLock,
- FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
@@ -241,7 +230,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
DialogDelegate<SystemUIDialog> delegate) {
super(context, theme);
mContext = context;
- mFeatureFlags = featureFlags;
mDelegate = delegate;
applyFlags(this);
@@ -269,7 +257,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
for (int i = 0; i < mOnCreateRunnables.size(); i++) {
mOnCreateRunnables.get(i).run();
}
- if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) {
+ if (predictiveBackAnimateDialogs()) {
DialogKt.registerAnimationOnBackInvoked(
/* dialog = */ this,
/* targetView = */ getWindow().getDecorView()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index d91ca92747f3..f3e8f62ddb5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -20,7 +20,6 @@ import android.content.Context
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.model.SysUiState
import com.android.systemui.util.Assert
import javax.inject.Inject
@@ -30,7 +29,6 @@ class SystemUIDialogFactory
@Inject
constructor(
@Application val applicationContext: Context,
- private val featureFlags: FeatureFlagsClassic,
private val dialogManager: SystemUIDialogManager,
private val sysUiState: SysUiState,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -57,7 +55,6 @@ constructor(
context,
theme,
dismissOnDeviceLock,
- featureFlags,
dialogManager,
sysUiState,
broadcastDispatcher,
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 e1fd37f558ab..89a2fb78635b 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
@@ -29,6 +29,8 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
+import com.android.systemui.statusbar.pipeline.icons.shared.BindableIconsRegistry
+import com.android.systemui.statusbar.pipeline.icons.shared.BindableIconsRegistryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
@@ -42,6 +44,8 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
@@ -76,8 +80,16 @@ abstract class StatusBarPipelineModule {
abstract fun airplaneModeViewModel(impl: AirplaneModeViewModelImpl): AirplaneModeViewModel
@Binds
+ abstract fun bindableIconsRepository(impl: BindableIconsRegistryImpl): BindableIconsRegistry
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
+ @Binds
+ abstract fun deviceBasedSatelliteRepository(
+ impl: DeviceBasedSatelliteRepositoryImpl
+ ): DeviceBasedSatelliteRepository
+
@Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
@Binds abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
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
new file mode 100644
index 000000000000..e3c3139f6906
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.icons.shared
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import javax.inject.Inject
+
+/**
+ * Bindable status bar icons represent icon descriptions which can be registered with
+ * StatusBarIconController and can also create their own bindings. A bound icon is responsible for
+ * its own updates via the [repeatWhenAttached] view lifecycle utility. Thus,
+ * StatusBarIconController can (and will) ignore any call to setIcon.
+ *
+ * In other words, these icons are bound once (at controller init) and they will control their
+ * visibility on their own (while their overall appearance remains at the discretion of
+ * StatusBarIconController, via the ModernStatusBarViewBinding interface).
+ */
+interface BindableIconsRegistry {
+ val bindableIcons: List<BindableIcon>
+}
+
+@SysUISingleton
+class BindableIconsRegistryImpl
+@Inject
+constructor(
+/** Bindables go here */
+) : BindableIconsRegistry {
+ /**
+ * Adding the injected bindables to this list will get them registered with
+ * StatusBarIconController
+ */
+ override val bindableIcons: List<BindableIcon> = listOf()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/BindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/BindableIcon.kt
new file mode 100644
index 000000000000..9d0d8380da45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/BindableIcon.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.icons.shared.model
+
+/**
+ * A BindableIcon describes a status bar icon that can be housed in the [ModernStatusBarView]
+ * created by [initializer]. They can be registered statically for [BindableIconsRepositoryImpl].
+ *
+ * Typical usage would be to create an (@SysUISingleton) adapter class that implements the
+ * interface. For example:
+ * ```
+ * @SysuUISingleton
+ * class MyBindableIconAdapter
+ * @Inject constructor(
+ * // deps
+ * val viewModel: MyViewModel
+ * ) : BindableIcon {
+ * override val slot = "icon_slot_name"
+ *
+ * override val initializer = ModernStatusBarViewCreator() {
+ * SingleBindableStatusBarIconView.createView(context).also { iconView ->
+ * MyIconViewBinder.bind(iconView, viewModel)
+ * }
+ * }
+ *
+ * override fun shouldBind() = Flags.myFlag()
+ * }
+ * ```
+ *
+ * By defining this adapter (and injecting it into the repository), we get our icon registered with
+ * the legacy StatusBarIconController while proxying all updates to the view binder that is created
+ * elsewhere.
+ *
+ * Note that the initializer block defines a closure that can pull in the viewModel dependency
+ * without us having to store it directly in the icon controller.
+ */
+interface BindableIcon {
+ val slot: String
+ val initializer: ModernStatusBarViewCreator
+ val shouldBindIcon: Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/ModernStatusBarViewCreator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/ModernStatusBarViewCreator.kt
new file mode 100644
index 000000000000..dbd5c1d1dede
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/model/ModernStatusBarViewCreator.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.icons.shared.model
+
+import android.content.Context
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
+
+/**
+ * Defined as an interface (as opposed to a typealias) to simplify calling from java.
+ * [ModernStatusBarViewCreator.createAndBind] should return a constructed and bound
+ * [ModernStatusBarView].
+ */
+fun interface ModernStatusBarViewCreator {
+ fun createAndBind(context: Context): ModernStatusBarView
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index dad409316730..39135c70788d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -71,6 +71,12 @@ interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionModel>>
+ /**
+ * The current list of [MobileIconInteractor]s associated with the current list of
+ * [filteredSubscriptions]
+ */
+ val icons: StateFlow<List<MobileIconInteractor>>
+
/** True if the active mobile data subscription has data enabled */
val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
@@ -259,6 +265,13 @@ constructor(
}
}
+ override val icons =
+ filteredSubscriptions
+ .mapLatest { subs ->
+ subs.map { getMobileConnectionInteractorForSubId(it.subscriptionId) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
/**
* Copied from the old pipeline. We maintain a 2s period of time where we will keep the
* validated bit from the old active network (A) while data is changing to the new one (B).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
new file mode 100644
index 000000000000..ad8b8100f14d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.data
+
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Device-based satellite refers to the capability of a device to connect directly to a satellite
+ * network. This is in contrast to carrier-based satellite connectivity, which is a property of a
+ * given mobile data subscription.
+ */
+interface DeviceBasedSatelliteRepository {
+ /** See [SatelliteConnectionState] for available states */
+ val connectionState: Flow<SatelliteConnectionState>
+
+ /** 0-4 level (similar to wifi and mobile) */
+ // @IntRange(from = 0, to = 4)
+ val signalStrength: Flow<Int>
+
+ /** Clients must observe this property, as device-based satellite is location-dependent */
+ val isSatelliteAllowedForCurrentLocation: Flow<Boolean>
+}
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
new file mode 100644
index 000000000000..8fc8b2f31366
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -0,0 +1,268 @@
+/*
+ * 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.data.prod
+
+import android.os.OutcomeReceiver
+import android.telephony.satellite.NtnSignalStrengthCallback
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteStateCallback
+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.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
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Unknown
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.time.SystemClock
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+/**
+ * A SatelliteManager that has responded that it has satellite support. Use [SatelliteSupport] to
+ * get one
+ */
+private typealias SupportedSatelliteManager = SatelliteManager
+
+/**
+ * "Supported" here means supported by the device. The value of this should be stable during the
+ * process lifetime.
+ */
+private sealed interface SatelliteSupport {
+ /** Not yet fetched */
+ data object Unknown : SatelliteSupport
+
+ /**
+ * SatelliteManager says that this mode is supported. Note that satellite manager can never be
+ * null now
+ */
+ data class Supported(val satelliteManager: SupportedSatelliteManager) : SatelliteSupport
+
+ /**
+ * Either we were told that there is no support for this feature, or the manager is null, or
+ * some other exception occurred while querying for support.
+ */
+ data object NotSupported : SatelliteSupport
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ companion object {
+ /** Convenience function to switch to the supported flow */
+ fun <T> Flow<SatelliteSupport>.whenSupported(
+ supported: (SatelliteManager) -> Flow<T>,
+ orElse: Flow<T>,
+ ): Flow<T> = flatMapLatest {
+ when (it) {
+ is Supported -> supported(it.satelliteManager)
+ else -> orElse
+ }
+ }
+ }
+}
+
+/**
+ * Basically your everyday run-of-the-mill system service listener, with three notable exceptions.
+ *
+ * First, there is an availability bit that we are tracking via [SatelliteManager]. See
+ * [isSatelliteAllowedForCurrentLocation] for the implementation details. The thing to note about
+ * this bit is that there is no callback that exists. Therefore we implement a simple polling
+ * mechanism here. Since the underlying bit is location-dependent, we simply poll every hour (see
+ * [POLLING_INTERVAL_MS]) and see what the current state is.
+ *
+ * Secondly, there are cases when simply requesting information from SatelliteManager can fail. See
+ * [SatelliteSupport] for details on how we track the state. What's worth noting here is that
+ * SUPPORTED is a stronger guarantee than [satelliteManager] being null. Therefore, the fundamental
+ * data flows here ([connectionState], [signalStrength],...) are wrapped in the convenience method
+ * [SatelliteSupport.whenSupported]. By defining flows as simple functions based on a
+ * [SupportedSatelliteManager], we can guarantee that the manager is non-null AND that it has told
+ * us that satellite is supported. Therefore, we don't expect exceptions to be thrown.
+ *
+ * Lastly, this class is designed to wait a full minute of process uptime before making any requests
+ * to the satellite manager. The hope is that by waiting we don't have to retry due to a modem that
+ * is still booting up or anything like that. We can tune or remove this behavior in the future if
+ * necessary.
+ */
+@SysUISingleton
+class DeviceBasedSatelliteRepositoryImpl
+@Inject
+constructor(
+ satelliteManagerOpt: Optional<SatelliteManager>,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val systemClock: SystemClock,
+) : DeviceBasedSatelliteRepository {
+
+ private val satelliteManager: SatelliteManager?
+
+ override val isSatelliteAllowedForCurrentLocation: MutableStateFlow<Boolean>
+
+ // 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)
+
+ init {
+ satelliteManager = satelliteManagerOpt.getOrNull()
+
+ isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+ if (satelliteManager != null) {
+ // First, check that satellite is supported on this device
+ scope.launch {
+ ensureMinUptime(systemClock, MIN_UPTIME)
+ satelliteSupport.value = satelliteManager.checkSatelliteSupported()
+
+ // We only need to check location availability if this mode is supported
+ if (satelliteSupport.value is Supported) {
+ isSatelliteAllowedForCurrentLocation.subscriptionCount
+ .map { it > 0 }
+ .distinctUntilChanged()
+ .collectLatest { hasSubscribers ->
+ if (hasSubscribers) {
+ /*
+ * As there is no listener available for checking satellite allowed,
+ * we must poll. Defaulting to polling at most once every hour while
+ * active. Subsequent OOS events will restart the job, so a flaky
+ * connection might cause more frequent checks.
+ */
+ while (true) {
+ checkIsSatelliteAllowed()
+ delay(POLLING_INTERVAL_MS)
+ }
+ }
+ }
+ }
+ }
+ } else {
+ satelliteSupport.value = NotSupported
+ }
+ }
+
+ override val connectionState =
+ satelliteSupport.whenSupported(
+ supported = ::connectionStateFlow,
+ orElse = flowOf(SatelliteConnectionState.Off)
+ )
+
+ // By using the SupportedSatelliteManager here, we expect registration never to fail
+ private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
+ conflatedCallbackFlow {
+ val cb = SatelliteStateCallback { state ->
+ trySend(SatelliteConnectionState.fromModemState(state))
+ }
+
+ sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
+
+ awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) }
+ }
+ .flowOn(bgDispatcher)
+
+ override val signalStrength =
+ satelliteSupport.whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0))
+
+ // By using the SupportedSatelliteManager here, we expect registration never to fail
+ private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
+ conflatedCallbackFlow {
+ val cb = NtnSignalStrengthCallback { signalStrength ->
+ trySend(signalStrength.level)
+ }
+
+ sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
+
+ awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) }
+ }
+ .flowOn(bgDispatcher)
+
+ /** Fire off a request to check for satellite availability. Always runs on the bg context */
+ private suspend fun checkIsSatelliteAllowed() =
+ withContext(bgDispatcher) {
+ satelliteManager?.requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ 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)
+ isSatelliteAllowedForCurrentLocation.value = false
+ }
+
+ override fun onResult(allowed: Boolean) {
+ isSatelliteAllowedForCurrentLocation.value = allowed
+ }
+ }
+ )
+ }
+
+ private suspend fun SatelliteManager.checkSatelliteSupported(): SatelliteSupport =
+ suspendCancellableCoroutine { continuation ->
+ val cb =
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(supported: Boolean) {
+ continuation.resume(
+ if (supported) {
+ Supported(satelliteManager = this@checkSatelliteSupported)
+ } else {
+ NotSupported
+ }
+ )
+ }
+
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ // Assume that an error means it's not supported
+ continuation.resume(NotSupported)
+ }
+ }
+
+ requestIsSatelliteSupported(bgDispatcher.asExecutor(), cb)
+ }
+
+ companion object {
+ // TTL for satellite polling is one hour
+ const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60
+
+ // Let the system boot up and stabilize before we check for system support
+ const val MIN_UPTIME: Long = 1000 * 60
+
+ private const val TAG = "DeviceBasedSatelliteRepo"
+
+ /** If our process hasn't been up for at least MIN_UPTIME, delay until we reach that time */
+ private suspend fun ensureMinUptime(clock: SystemClock, uptime: Long) {
+ val timeTilMinUptime =
+ uptime - (clock.uptimeMillis() - android.os.Process.getStartUptimeMillis())
+ if (timeTilMinUptime > 0) {
+ delay(timeTilMinUptime)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
new file mode 100644
index 000000000000..877957733d67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.domain.interactor
+
+import com.android.internal.telephony.flags.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class DeviceBasedSatelliteInteractor
+@Inject
+constructor(
+ val repo: DeviceBasedSatelliteRepository,
+ iconsInteractor: MobileIconsInteractor,
+ @Application scope: CoroutineScope,
+) {
+ /** Must be observed by any UI showing Satellite iconography */
+ val isSatelliteAllowed =
+ if (Flags.oemEnabledSatelliteFlag()) {
+ repo.isSatelliteAllowedForCurrentLocation
+ } else {
+ flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ /** See [SatelliteConnectionState] for relevant states */
+ val connectionState =
+ if (Flags.oemEnabledSatelliteFlag()) {
+ repo.connectionState
+ } else {
+
+ flowOf(SatelliteConnectionState.Off)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SatelliteConnectionState.Off)
+
+ /** 0-4 description of the connection strength */
+ val signalStrength =
+ if (Flags.oemEnabledSatelliteFlag()) {
+ repo.signalStrength
+ } else {
+ flowOf(0)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ /** When all connections are considered OOS, satellite connectivity is potentially valid */
+ val areAllConnectionsOutOfService =
+ if (Flags.oemEnabledSatelliteFlag()) {
+ iconsInteractor.icons.aggregateOver(selector = { intr -> intr.isInService }) {
+ isInServiceList ->
+ isInServiceList.all { !it }
+ }
+ } else {
+ flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+}
+
+/**
+ * aggregateOver allows us to combine over the leaf-nodes of successive lists emitted from the
+ * top-level flow. Re-emits if the list changes, or any of the intermediate values change.
+ *
+ * Provides a way to connect the reactivity of the top-level flow with the reactivity of an
+ * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver(
+ crossinline selector: (R) -> Flow<S>,
+ crossinline transform: (Array<S>) -> T
+): Flow<T> {
+ return map { list -> list.map { selector(it) } }
+ .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt
new file mode 100644
index 000000000000..bfe294119e64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/shared/model/SatelliteConnectionState.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.shared.model
+
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_LISTENING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+
+enum class SatelliteConnectionState {
+ // State is unknown or undefined
+ Unknown,
+ // Radio is off
+ Off,
+ // Radio is on, but not yet connected
+ On,
+ // Radio is connected, aka satellite is available for use
+ Connected;
+
+ companion object {
+ // TODO(b/316635648): validate these states. We don't need the level of granularity that
+ // SatelliteManager gives us.
+ fun fromModemState(@SatelliteManager.SatelliteModemState modemState: Int) =
+ when (modemState) {
+ // Transferring data is connected
+ SATELLITE_MODEM_STATE_CONNECTED,
+ SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
+ SATELLITE_MODEM_STATE_DATAGRAM_RETRYING -> Connected
+
+ // Modem is on but not connected
+ SATELLITE_MODEM_STATE_IDLE,
+ SATELLITE_MODEM_STATE_LISTENING,
+ SATELLITE_MODEM_STATE_NOT_CONNECTED -> On
+
+ // Consider unavailable equivalent to Off
+ SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SATELLITE_MODEM_STATE_OFF -> Off
+
+ // Else, we don't know what's up
+ SATELLITE_MODEM_STATE_UNKNOWN -> Unknown
+ else -> Unknown
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 41ed76d7edb1..45078e32108d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -20,6 +20,7 @@ import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.os.BatteryManager.EXTRA_PRESENT;
+
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -61,6 +62,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import javax.annotation.concurrent.GuardedBy;
@@ -448,50 +450,38 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
firePowerSaveChanged();
}
- protected void fireBatteryLevelChanged() {
- mLogger.logBatteryLevelChangedCallback(mLevel, mPluggedIn, mCharging);
+ protected final void dispatchSafeChange(Consumer<BatteryStateChangeCallback> action) {
+ ArrayList<BatteryStateChangeCallback> copy;
synchronized (mChangeCallbacks) {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
- }
+ copy = new ArrayList<>(mChangeCallbacks);
}
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ action.accept(copy.get(i));
+ }
+ }
+
+ protected void fireBatteryLevelChanged() {
+ mLogger.logBatteryLevelChangedCallback(mLevel, mPluggedIn, mCharging);
+ dispatchSafeChange(
+ (callback) -> callback.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging));
}
private void fireBatteryUnknownStateChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
- }
- }
+ dispatchSafeChange((callback) -> callback.onBatteryUnknownStateChanged(mStateUnknown));
}
private void firePowerSaveChanged() {
- synchronized (mChangeCallbacks) {
- final int N = mChangeCallbacks.size();
- for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
- }
- }
+ dispatchSafeChange((callback) -> callback.onPowerSaveChanged(mPowerSave));
}
private void fireIsBatteryDefenderChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onIsBatteryDefenderChanged(mIsBatteryDefender);
- }
- }
+ dispatchSafeChange((callback) -> callback.onIsBatteryDefenderChanged(mIsBatteryDefender));
}
private void fireIsIncompatibleChargingChanged() {
- synchronized (mChangeCallbacks) {
- final int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
- }
- }
+ dispatchSafeChange(
+ (callback) -> callback.onIsIncompatibleChargingChanged(mIsIncompatibleCharging));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 53b343c09329..fc2f6e958b32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -436,6 +436,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
@Override
public void onServiceDisconnected() {}
+ // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+ // sequential, so no concurrent exceptions
private final class H extends Handler {
private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index b06ebe9b9358..149c8fa6f4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -35,9 +35,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.util.Utils;
import java.io.PrintWriter;
@@ -291,11 +291,12 @@ public class CastControllerImpl implements CastController {
@VisibleForTesting
void fireOnCastDevicesChanged() {
+ final ArrayList<Callback> callbacks;
synchronized (mCallbacks) {
- for (Callback callback : mCallbacks) {
- fireOnCastDevicesChanged(callback);
- }
-
+ callbacks = new ArrayList<>(mCallbacks);
+ }
+ for (Callback callback : callbacks) {
+ fireOnCastDevicesChanged(callback);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 8207012af6cc..6319781991e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -36,10 +36,12 @@ public class DataSaverControllerImpl implements DataSaverController {
}
private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+ ArrayList<DataSaverController.Listener> copy;
synchronized (mListeners) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDataSaverChanged(isDataSaving);
- }
+ copy = new ArrayList<>(mListeners);
+ }
+ for (int i = 0; i < copy.size(); i++) {
+ copy.get(i).onDataSaverChanged(isDataSaving);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 5dcafb37f57e..b98eff8300a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -202,10 +202,12 @@ public class FlashlightControllerImpl implements FlashlightController {
private void dispatchListeners(int message, boolean argument) {
synchronized (mListeners) {
- final int N = mListeners.size();
+ final ArrayList<WeakReference<FlashlightController.FlashlightListener>> copy =
+ new ArrayList<>(mListeners);
+ final int n = copy.size();
boolean cleanup = false;
- for (int i = 0; i < N; i++) {
- FlashlightListener l = mListeners.get(i).get();
+ for (int i = 0; i < n; i++) {
+ FlashlightListener l = copy.get(i).get();
if (l != null) {
if (message == DISPATCH_ERROR) {
l.onFlashlightError();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index fffd839fcf11..87dfc9962675 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -119,7 +119,8 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
mHardwareToggleState.put(sensor, enabled);
}
- for (Callback callback : mCallbacks) {
+ Set<Callback> copy = new ArraySet<>(mCallbacks);
+ for (Callback callback : copy) {
callback.onSensorBlockedChanged(sensor, isSensorBlocked(sensor));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index e5f72ebdaab1..9eee5d00f708 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -356,6 +356,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
updateActiveLocationRequests();
}
+ // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+ // sequential, so no concurrent exceptions
private final class H extends Handler {
private static final int MSG_LOCATION_SETTINGS_CHANGED = 1;
private static final int MSG_LOCATION_ACTIVE_CHANGED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 63b9ff9717d6..b7d8ee3943e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -124,9 +124,10 @@ public class NextAlarmControllerImpl extends BroadcastReceiver
}
private void fireNextAlarmChanged() {
- int n = mChangeCallbacks.size();
+ ArrayList<NextAlarmChangeCallback> copy = new ArrayList<>(mChangeCallbacks);
+ int n = copy.size();
for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+ copy.get(i).onNextAlarmChanged(mNextAlarm);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
index f3d183ceb45f..0176abdf1579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
@@ -100,10 +100,13 @@ public class SafetyController implements
}
private void handleSafetyCenterEnableChange() {
+ final ArrayList<SafetyController.Listener> copy;
synchronized (mListeners) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
- }
+ copy = new ArrayList<>(mListeners);
+ }
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ copy.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4a4d4e1f27b2..5d69f367d77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -50,12 +50,12 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -429,10 +429,12 @@ public class SecurityControllerImpl implements SecurityController {
}
private void fireCallbacks() {
+ final ArrayList<SecurityControllerCallback> copy;
synchronized (mCallbacks) {
- for (SecurityControllerCallback callback : mCallbacks) {
- callback.onStateChanged();
- }
+ copy = new ArrayList<>(mCallbacks);
+ }
+ for (SecurityControllerCallback callback : copy) {
+ callback.onStateChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 66bf527f5047..df210b073e77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -48,12 +48,12 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -243,46 +243,43 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
}
private void fireNextAlarmChanged() {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onNextAlarmChanged());
- }
+ fireSafeChange(Callback::onNextAlarmChanged);
}
private void fireEffectsSuppressorChanged() {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onEffectsSupressorChanged());
- }
+ fireSafeChange(Callback::onEffectsSupressorChanged);
}
private void fireZenChanged(int zen) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onZenChanged(zen));
- }
+ fireSafeChange(c -> c.onZenChanged(zen));
}
private void fireZenAvailableChanged(boolean available) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onZenAvailableChanged(available));
- }
+ fireSafeChange(c -> c.onZenAvailableChanged(available));
}
private void fireManualRuleChanged(ZenRule rule) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onManualRuleChanged(rule));
- }
+ fireSafeChange(c -> c.onManualRuleChanged(rule));
}
private void fireConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+ fireSafeChange(c -> c.onConsolidatedPolicyChanged(policy));
+ }
+
+ private void fireSafeChange(Consumer<Callback> action) {
+ final ArrayList<Callback> copy;
synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onConsolidatedPolicyChanged(policy));
+ copy = new ArrayList<>(mCallbacks);
+ }
+ final int n = copy.size();
+ for (int i = 0; i < n; i++) {
+ action.accept(copy.get(i));
}
}
@VisibleForTesting
protected void fireConfigChanged(ZenModeConfig config) {
- synchronized (mCallbacksLock) {
- Utils.safeForeach(mCallbacks, c -> c.onConfigChanged(config));
- }
+ fireSafeChange(c -> c.onConfigChanged(config));
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
index ac04d31041b6..4f7dce363a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ReferenceExt.kt
@@ -2,6 +2,7 @@ package com.android.systemui.util
import java.lang.ref.SoftReference
import java.lang.ref.WeakReference
+import java.util.concurrent.atomic.AtomicReference
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@@ -48,3 +49,25 @@ fun <T> softReference(obj: T? = null): ReadWriteProperty<Any?, T?> {
}
}
}
+
+/**
+ * Creates a nullable Kotlin idiomatic [AtomicReference].
+ *
+ * Usage:
+ * ```
+ * var atomicReferenceObj: Object? by nullableAtomicReference(null)
+ * atomicReferenceObj = Object()
+ * ```
+ */
+fun <T> nullableAtomicReference(obj: T? = null): ReadWriteProperty<Any?, T?> {
+ return object : ReadWriteProperty<Any?, T?> {
+ val t = AtomicReference(obj)
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
+ return t.get()
+ }
+
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
+ t.set(value)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index df5162af70c5..3d724e1caa5d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -22,12 +22,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
+import android.util.IndentingPrintWriter;
import android.util.Log;
+import androidx.annotation.NonNull;
+
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.annotations.WeaklyReferencedCallback;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -244,6 +249,21 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
});
}
+ void dump(@NonNull PrintWriter pw) {
+ IndentingPrintWriter ipw = DumpUtilsKt.asIndenting(pw);
+ ipw.println("ObservableServiceConnection state:");
+ DumpUtilsKt.withIncreasedIndent(ipw, () -> {
+ ipw.println("mServiceIntent: " + mServiceIntent);
+ ipw.println("mLastDisconnectReason: " + mLastDisconnectReason.orElse(-1));
+ ipw.println("Callbacks:");
+ DumpUtilsKt.withIncreasedIndent(ipw, () -> {
+ for (WeakReference<Callback<T>> cbRef : mCallbacks) {
+ ipw.println(cbRef.get());
+ }
+ });
+ });
+ }
+
private void applyToCallbacksLocked(Consumer<Callback<T>> applicator) {
final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 6e19bed49626..9b72eb710588 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -17,6 +17,7 @@
package com.android.systemui.util.service;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
+import static com.android.systemui.util.service.dagger.ObservableServiceModule.DUMPSYS_NAME;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
@@ -24,9 +25,15 @@ import static com.android.systemui.util.service.dagger.ObservableServiceModule.S
import android.util.Log;
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
+import java.io.PrintWriter;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -35,7 +42,7 @@ import javax.inject.Named;
* {@link ObservableServiceConnection}.
* @param <T> The transformed connection type handled by the service.
*/
-public class PersistentConnectionManager<T> {
+public class PersistentConnectionManager<T> implements Dumpable {
private static final String TAG = "PersistentConnManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -45,6 +52,8 @@ public class PersistentConnectionManager<T> {
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
private final Observer mObserver;
+ private final DumpManager mDumpManager;
+ private final String mDumpsysName;
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
@@ -89,6 +98,8 @@ public class PersistentConnectionManager<T> {
public PersistentConnectionManager(
SystemClock clock,
DelayableExecutor mainExecutor,
+ DumpManager dumpManager,
+ @Named(DUMPSYS_NAME) String dumpsysName,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
@Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
@@ -98,6 +109,8 @@ public class PersistentConnectionManager<T> {
mMainExecutor = mainExecutor;
mConnection = serviceConnection;
mObserver = observer;
+ mDumpManager = dumpManager;
+ mDumpsysName = TAG + "#" + dumpsysName;
mMaxReconnectAttempts = maxReconnectAttempts;
mBaseReconnectDelayMs = baseReconnectDelayMs;
@@ -108,6 +121,7 @@ public class PersistentConnectionManager<T> {
* Begins the {@link PersistentConnectionManager} by connecting to the associated service.
*/
public void start() {
+ mDumpManager.registerCriticalDumpable(mDumpsysName, this);
mConnection.addCallback(mConnectionCallback);
mObserver.addCallback(mObserverCallback);
initiateConnectionAttempt();
@@ -120,6 +134,32 @@ public class PersistentConnectionManager<T> {
mConnection.removeCallback(mConnectionCallback);
mObserver.removeCallback(mObserverCallback);
mConnection.unbind();
+ mDumpManager.unregisterDumpable(mDumpsysName);
+ }
+
+ /**
+ * Add a callback to the {@link ObservableServiceConnection}.
+ * @param callback The callback to add.
+ */
+ public void addConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
+ mConnection.addCallback(callback);
+ }
+
+ /**
+ * Remove a callback from the {@link ObservableServiceConnection}.
+ * @param callback The callback to remove.
+ */
+ public void removeConnectionCallback(ObservableServiceConnection.Callback<T> callback) {
+ mConnection.removeCallback(callback);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mMaxReconnectAttempts: " + mMaxReconnectAttempts);
+ pw.println("mBaseReconnectDelayMs: " + mBaseReconnectDelayMs);
+ pw.println("mMinConnectionDuration: " + mMinConnectionDuration);
+ pw.println("mReconnectAttempts: " + mReconnectAttempts);
+ mConnection.dump(pw);
}
private void initiateConnectionAttempt() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
index bcf34f833d32..c52c524d1fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java
@@ -19,14 +19,14 @@ package com.android.systemui.util.service.dagger;
import android.content.res.Resources;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
-
-import javax.inject.Named;
+import com.android.systemui.res.R;
import dagger.Module;
import dagger.Provides;
+import javax.inject.Named;
+
/**
* Module containing components and parameters for
* {@link com.android.systemui.util.service.ObservableServiceConnection}
@@ -41,6 +41,7 @@ public class ObservableServiceModule {
public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
public static final String SERVICE_CONNECTION = "service_connection";
public static final String OBSERVER = "observer";
+ public static final String DUMPSYS_NAME = "dumpsys_name";
@Provides
@Named(MAX_RECONNECT_ATTEMPTS)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 342494d8997b..46936d6223a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.decor.FaceScanningProviderFactory
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.log.logcatLogBuffer
@@ -53,6 +54,8 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val facePropertyRepository = FakeFacePropertyRepository()
+
private val displayId = 2
@Before
@@ -86,9 +89,10 @@ class FaceScanningProviderFactoryTest : SysuiTestCase() {
keyguardUpdateMonitor,
mock(Executor::class.java),
ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+ facePropertyRepository,
)
- whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+ facePropertyRepository.setSensorLocation(Point(10, 10))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c094df5dd569..c07148b32cf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -54,6 +54,7 @@ import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Path;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
@@ -80,6 +81,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository;
import com.android.systemui.decor.CornerDecorProvider;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.CutoutDecorProviderImpl;
@@ -101,6 +103,7 @@ import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -108,8 +111,6 @@ import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -169,8 +170,11 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
@Mock
private CutoutDecorProviderFactory mCutoutFactory;
- @Captor
- private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
+ @Mock
+ private JavaAdapter mJavaAdapter;
+
+ private FakeFacePropertyRepository mFakeFacePropertyRepository =
+ new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
@Before
@@ -227,20 +231,23 @@ public class ScreenDecorationsTest extends SysuiTestCase {
doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
+ mFakeFacePropertyRepository.setSensorLocation(new Point(10, 10));
+
mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mExecutor,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mFakeFacePropertyRepository));
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
mThreadFactory,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mAuthController) {
+ mFakeFacePropertyRepository, mJavaAdapter) {
@Override
public void start() {
super.start();
@@ -1235,9 +1242,9 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
mDotViewController,
mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
- new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+ mFakeFacePropertyRepository, mJavaAdapter);
screenDecorations.start();
- verify(mAuthController).addCallback(mAuthControllerCallback.capture());
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
@Override
@@ -1252,9 +1259,9 @@ public class ScreenDecorationsTest extends SysuiTestCase {
});
mExecutor.runAllReady();
clearInvocations(mFaceScanningDecorProvider);
-
- AuthController.Callback callback = mAuthControllerCallback.getValue();
- callback.onFaceSensorLocationChanged();
+ final Point location = new Point();
+ mFakeFacePropertyRepository.setSensorLocation(location);
+ screenDecorations.onFaceSensorLocationChanged(location);
mExecutor.runAllReady();
verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index c52571188256..9b6c8cdf7f9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -25,14 +25,12 @@ import android.view.ViewGroup
import android.widget.Button
import android.widget.SeekBar
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
@@ -78,7 +76,6 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
@Mock private lateinit var dialogManager: SystemUIDialogManager
@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var userTracker: UserTracker
- private val featureFlags = FakeFeatureFlags()
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@@ -88,7 +85,6 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
val mainHandler = Handler(testableLooper.looper)
systemSettings = FakeSettings()
- featureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true)
// Guarantee that the systemSettings always starts with the default font scale.
systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
secureSettings = FakeSettings()
@@ -96,29 +92,32 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
backgroundDelayableExecutor = FakeExecutor(systemClock)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
- fontScalingDialogDelegate = spy(FontScalingDialogDelegate(
+ fontScalingDialogDelegate =
+ spy(
+ FontScalingDialogDelegate(
+ mContext,
+ dialogFactory,
+ LayoutInflater.from(mContext),
+ systemSettings,
+ secureSettings,
+ systemClock,
+ userTracker,
+ mainHandler,
+ backgroundDelayableExecutor
+ )
+ )
+
+ dialog =
+ SystemUIDialog(
mContext,
- dialogFactory,
- LayoutInflater.from(mContext),
- systemSettings,
- secureSettings,
- systemClock,
- userTracker,
- mainHandler,
- backgroundDelayableExecutor
- ))
-
- dialog = SystemUIDialog(
- mContext,
- 0,
- DEFAULT_DISMISS_ON_DEVICE_LOCK,
- featureFlags,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogLaunchAnimator,
- fontScalingDialogDelegate
- )
+ 0,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogLaunchAnimator,
+ fontScalingDialogDelegate
+ )
whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
}
@@ -299,11 +298,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() {
// Default seekbar progress for font size is 1, simulate dragging to 0 without
// releasing the finger
changeListener.onStartTrackingTouch(seekBar)
- changeListener.onProgressChanged(
- seekBar,
- /* progress= */ 0,
- /* fromUser= */ false
- )
+ changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
backgroundDelayableExecutor.advanceClockToNext()
backgroundDelayableExecutor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index c143bc0aa06c..a47e28801709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -29,6 +29,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -93,6 +94,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock
private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
+ private val facePropertyRepository = FakeFacePropertyRepository()
private val displayMetrics = DisplayMetrics()
@Captor
@@ -126,6 +128,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
biometricUnlockController,
lightRevealScrim,
+ facePropertyRepository,
rippleView,
)
controller.init()
@@ -202,7 +205,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Test
fun testNullFaceSensorLocationDoesNothing() {
- `when`(authController.faceSensorLocation).thenReturn(null)
+ facePropertyRepository.setSensorLocation(null)
controller.onViewAttached()
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -270,7 +273,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
val faceLocation = Point(5, 5)
- `when`(authController.faceSensorLocation).thenReturn(faceLocation)
+ facePropertyRepository.setSensorLocation(faceLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index 2aeba9a09b84..3dcb3f89c730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.hardware.fingerprint.FingerprintSensorProperties;
import android.view.Surface;
import androidx.test.filters.SmallTest;
@@ -63,28 +64,32 @@ public class UdfpsUtilsTest extends SysuiTestCase {
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
// touch at 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
// touch at 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
}
@@ -97,28 +102,32 @@ public class UdfpsUtilsTest extends SysuiTestCase {
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 90 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
// touch at 180 degrees -> 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0 /* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
// touch at 270 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
}
@@ -131,28 +140,32 @@ public class UdfpsUtilsTest extends SysuiTestCase {
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[3]);
// touch at 90 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[0]);
// touch at 180 degrees -> 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[1]);
// touch at 270 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
- new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+ new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
)
).isEqualTo(mTouchHints[2]);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index 834179bf289d..a84778a49643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository
import android.hardware.devicestate.DeviceStateManager
import android.hardware.display.DisplayManager
import android.os.Handler
+import android.util.Size
import android.view.Display
import android.view.DisplayInfo
import android.view.Surface
@@ -147,6 +148,40 @@ class DisplayStateRepositoryTest : SysuiTestCase() {
displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
}
+
+ @Test
+ fun updatesCurrentSize_whenDisplayStateChanges() =
+ testScope.runTest {
+ val currentSize by collectLastValue(underTest.currentDisplaySize)
+ runCurrent()
+
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListenerCaptor.capture(),
+ same(handler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+ )
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_0
+ info.logicalWidth = 100
+ info.logicalHeight = 200
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+ assertThat(currentSize).isEqualTo(Size(100, 200))
+
+ whenever(display.getDisplayInfo(any())).then {
+ val info = it.getArgument<DisplayInfo>(0)
+ info.rotation = Surface.ROTATION_90
+ info.logicalWidth = 100
+ info.logicalHeight = 200
+ return@then true
+ }
+ displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+ assertThat(currentSize).isEqualTo(Size(200, 100))
+ }
}
private fun DeviceStateManager.captureCallback() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
index c14ad6a46616..9f24d5dfea79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -17,10 +17,12 @@
package com.android.systemui.biometrics.data.repository
+import android.graphics.Point
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED
import android.hardware.biometrics.SensorProperties
+import android.hardware.camera2.CameraManager
import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
@@ -28,9 +30,12 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.shared.model.LockoutMode
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
@@ -45,6 +50,7 @@ import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.any
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -53,23 +59,56 @@ import org.mockito.junit.MockitoRule
@SmallTest
@RunWith(JUnit4::class)
class FacePropertyRepositoryImplTest : SysuiTestCase() {
+ companion object {
+ private const val LOGICAL_CAMERA_ID_OUTER_FRONT = "0"
+ private const val LOGICAL_CAMERA_ID_INNER_FRONT = "1"
+ private const val PHYSICAL_CAMERA_ID_OUTER_FRONT = "5"
+ private const val PHYSICAL_CAMERA_ID_INNER_FRONT = "6"
+ private val OUTER_FRONT_SENSOR_LOCATION = intArrayOf(100, 10, 20)
+ private val INNER_FRONT_SENSOR_LOCATION = intArrayOf(200, 20, 30)
+ }
+
@JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private lateinit var underTest: FacePropertyRepository
private lateinit var dispatcher: TestDispatcher
private lateinit var testScope: TestScope
+ private val displayStateRepository = FakeDisplayStateRepository()
+ private val configurationRepository = FakeConfigurationRepository()
+
@Captor private lateinit var callback: ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
@Mock private lateinit var faceManager: FaceManager
+ @Captor private lateinit var cameraCallback: ArgumentCaptor<CameraManager.AvailabilityCallback>
+ @Mock private lateinit var cameraManager: CameraManager
@Before
fun setup() {
+ overrideResource(R.string.config_protectedCameraId, LOGICAL_CAMERA_ID_OUTER_FRONT)
+ overrideResource(R.string.config_protectedPhysicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+ overrideResource(R.string.config_protectedInnerCameraId, LOGICAL_CAMERA_ID_INNER_FRONT)
+ overrideResource(
+ R.string.config_protectedInnerPhysicalCameraId,
+ PHYSICAL_CAMERA_ID_INNER_FRONT
+ )
+ overrideResource(R.array.config_face_auth_props, OUTER_FRONT_SENSOR_LOCATION)
+ overrideResource(R.array.config_inner_face_auth_props, INNER_FRONT_SENSOR_LOCATION)
+
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
underTest = createRepository(faceManager)
}
private fun createRepository(manager: FaceManager? = faceManager) =
- FacePropertyRepositoryImpl(testScope.backgroundScope, dispatcher, manager)
+ FacePropertyRepositoryImpl(
+ context,
+ context.mainExecutor,
+ testScope.backgroundScope,
+ dispatcher,
+ manager,
+ cameraManager,
+ displayStateRepository,
+ configurationRepository,
+ )
@Test
fun whenFaceManagerIsNotPresentIsNull() =
@@ -129,6 +168,75 @@ class FacePropertyRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.NONE)
}
+ @Test
+ fun providesTheSensorLocationOfOuterCameraFromOnPhysicalCameraAvailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_OUTER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
+ @Test
+ fun providesTheSensorLocationOfInnerCameraFromOnPhysicalCameraAvailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(INNER_FRONT_SENSOR_LOCATION[0], INNER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
+ @Test
+ fun providesTheSensorLocationOfCameraFromOnPhysicalCameraUnavailable() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.sensorLocation)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraUnavailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+ runCurrent()
+
+ val sensorLocation by collectLastValue(underTest.sensorLocation)
+ assertThat(sensorLocation)
+ .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+ }
+ }
+
private fun createSensorProperties(id: Int, strength: Int) =
FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 3ff43c6a3787..7d5aec6b7d4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.bluetooth;
import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
@@ -40,8 +42,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
@@ -72,7 +72,6 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase {
LocalBluetoothLeBroadcast.class);
private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
private BroadcastDialogDelegate mBroadcastDialogDelegate;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock SystemUIDialog.Factory mSystemUIDialogFactory;
@Mock SystemUIDialogManager mDialogManager;
@Mock SysUiState mSysUiState;
@@ -91,7 +90,6 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase {
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
@@ -110,7 +108,6 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase {
mContext,
0,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
- mFeatureFlags,
mDialogManager,
mSysUiState,
getFakeBroadcastDispatcher(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 0a5b124cdb7b..fb101dda6aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -78,7 +78,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mMediaData = new MediaData(
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
- MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+ MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L,
InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index 35bf7753358e..dc211303e52c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -83,7 +83,6 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
when(mSystemUIDialog.getContext()).thenReturn(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7ce51aea90e2..86ab01ca9e2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -105,7 +105,6 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
spy(
SystemUIDialog.Factory(
context,
- flags,
systemUIDialogManager,
sysuiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index cb90cc53f0f0..0ba99f2d24fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -44,7 +44,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
@@ -113,7 +112,6 @@ public class RecordingControllerTest extends SysuiTestCase {
mDialogFactory = new TestSystemUIDialogFactory(
mContext,
- mFeatureFlags,
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
@@ -313,14 +311,12 @@ public class RecordingControllerTest extends SysuiTestCase {
TestSystemUIDialogFactory(
Context context,
- FeatureFlags featureFlags,
SystemUIDialogManager systemUIDialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
super(
context,
- featureFlags,
systemUIDialogManager,
sysUiState,
broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 8f696e7f11ad..23995364f9e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -74,7 +74,6 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
- Dependency.get(FeatureFlags::class.java),
Dependency.get(SystemUIDialogManager::class.java),
Dependency.get(SysUiState::class.java),
Dependency.get(BroadcastDispatcher::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 313276727caf..a20658197a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -152,7 +152,9 @@ import com.android.systemui.statusbar.notification.ConversationNotificationManag
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -586,6 +588,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
+ NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository =
+ new NotificationsKeyguardViewStateRepository();
+ NotificationsKeyguardInteractor notifsKeyguardInteractor =
+ new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mDumpManager,
@@ -596,7 +602,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
- new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()));
+ new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
+ notifsKeyguardInteractor);
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 438b33d9afbc..039fef9c1df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -22,12 +22,14 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -54,6 +56,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()
+ private val kosmos = Kosmos()
+
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -100,6 +104,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
dozeParameters,
screenOffAnimationController,
logger,
+ kosmos.notificationsKeyguardInteractor,
)
statusBarStateCallback = withArgCaptor {
verify(statusBarStateController).addCallback(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
deleted file mode 100644
index 170f651aed91..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ /dev/null
@@ -1,88 +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.statusbar.notification.data.repository
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() {
-
- @SysUISingleton
- @Component(modules = [SysUITestModule::class])
- interface TestComponent : SysUITestComponent<NotificationsKeyguardViewStateRepositoryImpl> {
-
- val mockWakeUpCoordinator: NotificationWakeUpCoordinator
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- ): TestComponent
- }
- }
-
- private val testComponent: TestComponent =
- DaggerNotificationsKeyguardViewStateRepositoryTest_TestComponent.factory()
- .create(test = this)
-
- @Test
- fun areNotifsFullyHidden_reflectsWakeUpCoordinator() =
- testComponent.runTest {
- whenever(mockWakeUpCoordinator.notificationsFullyHidden).thenReturn(false)
- val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
- runCurrent()
-
- assertThat(notifsFullyHidden).isFalse()
-
- withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
- .onFullyHiddenChanged(true)
- runCurrent()
-
- assertThat(notifsFullyHidden).isTrue()
- }
-
- @Test
- fun isPulseExpanding_reflectsWakeUpCoordinator() =
- testComponent.runTest {
- whenever(mockWakeUpCoordinator.isPulseExpanding()).thenReturn(false)
- val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
- runCurrent()
-
- assertThat(isPulseExpanding).isFalse()
-
- withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
- .onPulseExpandingChanged(true)
- runCurrent()
-
- assertThat(isPulseExpanding).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
index bb3113a72e92..3593f5b4963e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractorTest.kt
@@ -21,7 +21,6 @@ import com.android.systemui.collectLastValue
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.runCurrent
import com.android.systemui.runTest
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -33,9 +32,6 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() {
@SysUISingleton
@Component(modules = [SysUITestModule::class])
interface TestComponent : SysUITestComponent<NotificationsKeyguardInteractor> {
-
- val repository: FakeNotificationsKeyguardViewStateRepository
-
@Component.Factory
interface Factory {
fun create(@BindsInstance test: SysuiTestCase): TestComponent
@@ -48,13 +44,13 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() {
@Test
fun areNotifsFullyHidden_reflectsRepository() =
testComponent.runTest {
- repository.setNotificationsFullyHidden(false)
+ underTest.setNotificationsFullyHidden(false)
val notifsFullyHidden by collectLastValue(underTest.areNotificationsFullyHidden)
runCurrent()
assertThat(notifsFullyHidden).isFalse()
- repository.setNotificationsFullyHidden(true)
+ underTest.setNotificationsFullyHidden(true)
runCurrent()
assertThat(notifsFullyHidden).isTrue()
@@ -63,13 +59,13 @@ class NotificationsKeyguardInteractorTest : SysuiTestCase() {
@Test
fun isPulseExpanding_reflectsRepository() =
testComponent.runTest {
- repository.setPulseExpanding(false)
+ underTest.setPulseExpanding(false)
val isPulseExpanding by collectLastValue(underTest.isPulseExpanding)
runCurrent()
assertThat(isPulseExpanding).isFalse()
- repository.setPulseExpanding(true)
+ underTest.setPulseExpanding(true)
runCurrent()
assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 47feccf4bdcf..7faf5628b40a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -29,8 +29,8 @@ import com.android.systemui.statusbar.data.repository.NotificationListenerSettin
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -61,7 +61,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
interface TestComponent : SysUITestComponent<NotificationIconsInteractor> {
val activeNotificationListRepository: ActiveNotificationListRepository
- val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
@Component.Factory
interface Factory {
@@ -136,7 +136,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
testComponent.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
- keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
}
@@ -144,7 +144,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() {
fun filteredEntrySet_noPulsing_notifsFullyHidden() =
testComponent.runTest {
val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
- keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
@@ -161,7 +161,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
val activeNotificationListRepository: ActiveNotificationListRepository
val deviceEntryRepository: FakeDeviceEntryRepository
- val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
@Component.Factory
interface Factory {
@@ -222,7 +222,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
testComponent.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
deviceEntryRepository.setBypassEnabled(false)
- keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@@ -231,7 +231,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
testComponent.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
deviceEntryRepository.setBypassEnabled(false)
- keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
@@ -240,7 +240,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
testComponent.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
deviceEntryRepository.setBypassEnabled(true)
- keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(false)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
}
@@ -249,7 +249,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
testComponent.runTest {
val filteredSet by collectLastValue(underTest.aodNotifs)
deviceEntryRepository.setBypassEnabled(true)
- keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
}
}
@@ -266,7 +266,7 @@ class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
val activeNotificationListRepository: ActiveNotificationListRepository
val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
- val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationsKeyguardInteractor: NotificationsKeyguardInteractor
val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
@Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
index 7eba3b463336..c44c178db38a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -22,11 +22,15 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -72,6 +76,34 @@ class ManagedProfileControllerImplTest : SysuiTestCase() {
Assert.assertEquals(true, controller.hasActiveProfile())
}
+ @Test
+ fun callbackRemovedWhileDispatching_doesntCrash() {
+ var remove = false
+ val callback =
+ object : ManagedProfileController.Callback {
+ override fun onManagedProfileChanged() {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+
+ override fun onManagedProfileRemoved() {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+ controller.addCallback(callback)
+ controller.addCallback(TestCallback)
+
+ remove = true
+ setupWorkingProfile(1)
+
+ val captor = argumentCaptor<UserTracker.Callback>()
+ verify(userTracker).addCallback(capture(captor), any())
+ captor.value.onProfilesChanged(userManager.getEnabledProfiles(1))
+ }
+
private fun setupWorkingProfile(userId: Int) {
`when`(userManager.getEnabledProfiles(userId))
.thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 6f04f369d52f..f6a8243c7a46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -23,6 +23,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX
+import com.android.systemui.statusbar.pipeline.icons.shared.BindableIconsRegistry
+import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
+import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -49,14 +52,15 @@ class StatusBarIconControllerImplTest : SysuiTestCase() {
iconList = StatusBarIconList(arrayOf())
underTest =
StatusBarIconControllerImpl(
- context,
- commandQueue,
- mock(),
- mock(),
- mock(),
- mock(),
- iconList,
- mock(),
+ /* context = */ context,
+ /* commandQueue = */ commandQueue,
+ /* demoModeController = */ mock(),
+ /* configurationController = */ mock(),
+ /* tunerService = */ mock(),
+ /* dumpManager = */ mock(),
+ /* statusBarIconList = */ iconList,
+ /* statusBarPipelineFlags = */ mock(),
+ /* modernIconsRegistry = */ mock(),
)
underTest.addIconGroup(iconGroup)
val commandQueueCallbacksCaptor = kotlinArgumentCaptor<CommandQueue.Callbacks>()
@@ -366,6 +370,31 @@ class StatusBarIconControllerImplTest : SysuiTestCase() {
assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX")
}
+ @Test
+ fun bindableIcons_addedOnInit() {
+ val fakeIcon = FakeBindableIcon("test_slot")
+
+ iconList = StatusBarIconList(arrayOf())
+
+ // WHEN there are registered icons
+ underTest =
+ StatusBarIconControllerImpl(
+ /* context = */ context,
+ /* commandQueue = */ commandQueue,
+ /* demoModeController = */ mock(),
+ /* configurationController = */ mock(),
+ /* tunerService = */ mock(),
+ /* dumpManager = */ mock(),
+ /* statusBarIconList = */ iconList,
+ /* statusBarPipelineFlags = */ mock(),
+ /* modernIconsRegistry = */ FakeBindableIconsRegistry(listOf(fakeIcon)),
+ )
+
+ // THEN they are properly added to the list on init
+ assertThat(iconList.getIconHolder("test_slot", 0))
+ .isInstanceOf(StatusBarIconHolder.BindableIconHolder::class.java)
+ }
+
private fun createExternalIcon(): StatusBarIcon {
return StatusBarIcon(
"external.package",
@@ -377,3 +406,20 @@ class StatusBarIconControllerImplTest : SysuiTestCase() {
)
}
}
+
+class FakeBindableIconsRegistry(
+ override val bindableIcons: List<BindableIcon>,
+) : BindableIconsRegistry
+
+class FakeBindableIcon(
+ override val slot: String,
+ override val shouldBindIcon: Boolean = true,
+) : BindableIcon {
+ // Track initialized so we can know that our icon was properly bound
+ var hasInitialized = false
+
+ override val initializer = ModernStatusBarViewCreator { _ ->
+ hasInitialized = true
+ mock()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 0ff6f200d402..ca316230fb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
+import com.android.systemui.statusbar.pipeline.icons.shared.BindableIconsRegistry;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
@@ -108,7 +109,8 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
mock(TunerService.class),
mock(DumpManager.class),
mock(StatusBarIconList.class),
- flags
+ flags,
+ mock(BindableIconsRegistry.class)
);
iconController.addIconGroup(manager);
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
new file mode 100644
index 000000000000..a906a8953e02
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -0,0 +1,391 @@
+/*
+ * 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.data.prod
+
+import android.os.OutcomeReceiver
+import android.os.Process
+import android.telephony.satellite.NtnSignalStrength
+import android.telephony.satellite.NtnSignalStrengthCallback
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_LISTENING
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteStateCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+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
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
+ private lateinit var underTest: DeviceBasedSatelliteRepositoryImpl
+
+ @Mock private lateinit var satelliteManager: SatelliteManager
+
+ private val systemClock = FakeSystemClock()
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun nullSatelliteManager_usesDefaultValues() =
+ testScope.runTest {
+ setupDefaultRepo()
+ underTest =
+ DeviceBasedSatelliteRepositoryImpl(
+ Optional.empty(),
+ dispatcher,
+ testScope.backgroundScope,
+ systemClock,
+ )
+
+ val connectionState by collectLastValue(underTest.connectionState)
+ val strength by collectLastValue(underTest.signalStrength)
+ val allowed by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+
+ assertThat(connectionState).isEqualTo(SatelliteConnectionState.Off)
+ assertThat(strength).isEqualTo(0)
+ assertThat(allowed).isFalse()
+ }
+
+ @Test
+ fun connectionState_mapsFromSatelliteModemState() =
+ testScope.runTest {
+ setupDefaultRepo()
+ val latest by collectLastValue(underTest.connectionState)
+ runCurrent()
+ val callback =
+ withArgCaptor<SatelliteStateCallback> {
+ verify(satelliteManager).registerForSatelliteModemStateChanged(any(), capture())
+ }
+
+ // Mapping from modem state to SatelliteConnectionState is rote, just run all of the
+ // possibilities here
+
+ // Off states
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_UNAVAILABLE)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+
+ // On states
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_IDLE)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.On)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_LISTENING)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.On)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_NOT_CONNECTED)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.On)
+
+ // Connected states
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_CONNECTED)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_DATAGRAM_RETRYING)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
+
+ // Unknown states
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_UNKNOWN)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Unknown)
+ // Garbage value (for completeness' sake)
+ callback.onSatelliteModemStateChanged(123456)
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Unknown)
+ }
+
+ @Test
+ fun signalStrength_readsSatelliteManagerState() =
+ testScope.runTest {
+ setupDefaultRepo()
+ val latest by collectLastValue(underTest.signalStrength)
+ runCurrent()
+ val callback =
+ withArgCaptor<NtnSignalStrengthCallback> {
+ verify(satelliteManager).registerForNtnSignalStrengthChanged(any(), capture())
+ }
+
+ assertThat(latest).isNull()
+
+ callback.onNtnSignalStrengthChanged(NtnSignalStrength(1))
+ assertThat(latest).isEqualTo(1)
+
+ callback.onNtnSignalStrengthChanged(NtnSignalStrength(2))
+ assertThat(latest).isEqualTo(2)
+
+ callback.onNtnSignalStrengthChanged(NtnSignalStrength(3))
+ assertThat(latest).isEqualTo(3)
+
+ callback.onNtnSignalStrengthChanged(NtnSignalStrength(4))
+ assertThat(latest).isEqualTo(4)
+ }
+
+ @Test
+ fun isSatelliteAllowed_readsSatelliteManagerState_enabled() =
+ testScope.runTest {
+ setupDefaultRepo()
+ // GIVEN satellite is allowed in this location
+ val allowed = true
+
+ doAnswer {
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onResult(allowed)
+ null
+ }
+ .`when`(satelliteManager)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ any(),
+ any<OutcomeReceiver<Boolean, SatelliteException>>()
+ )
+
+ val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isSatelliteAllowed_readsSatelliteManagerState_disabled() =
+ testScope.runTest {
+ setupDefaultRepo()
+ // GIVEN satellite is not allowed in this location
+ val allowed = false
+
+ doAnswer {
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onResult(allowed)
+ null
+ }
+ .`when`(satelliteManager)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ any(),
+ any<OutcomeReceiver<Boolean, SatelliteException>>()
+ )
+
+ val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSatelliteAllowed_pollsOnTimeout() =
+ testScope.runTest {
+ setupDefaultRepo()
+ // GIVEN satellite is not allowed in this location
+ var allowed = false
+
+ doAnswer {
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onResult(allowed)
+ null
+ }
+ .`when`(satelliteManager)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ any(),
+ any<OutcomeReceiver<Boolean, SatelliteException>>()
+ )
+
+ val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+
+ assertThat(latest).isFalse()
+
+ // WHEN satellite becomes enabled
+ allowed = true
+
+ // WHEN the timeout has not yet been reached
+ advanceTimeBy(POLLING_INTERVAL_MS / 2)
+
+ // THEN the value is still false
+ assertThat(latest).isFalse()
+
+ // WHEN time advances beyond the polling interval
+ advanceTimeBy(POLLING_INTERVAL_MS / 2 + 1)
+
+ // THEN then new value is emitted
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isSatelliteAllowed_pollingRestartsWhenCollectionRestarts() =
+ testScope.runTest {
+ setupDefaultRepo()
+ // Use the old school launch/cancel so we can simulate subscribers arriving and leaving
+
+ var latest: Boolean? = false
+ var job =
+ underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
+
+ // GIVEN satellite is not allowed in this location
+ var allowed = false
+
+ doAnswer {
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onResult(allowed)
+ null
+ }
+ .`when`(satelliteManager)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ any(),
+ any<OutcomeReceiver<Boolean, SatelliteException>>()
+ )
+
+ assertThat(latest).isFalse()
+
+ // WHEN satellite becomes enabled
+ allowed = true
+
+ // WHEN the job is restarted
+ advanceTimeBy(POLLING_INTERVAL_MS / 2)
+
+ job.cancel()
+ job =
+ underTest.isSatelliteAllowedForCurrentLocation.onEach { latest = it }.launchIn(this)
+
+ // THEN the value is re-fetched
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isSatelliteAllowed_falseWhenErrorOccurs() =
+ testScope.runTest {
+ setupDefaultRepo()
+ doAnswer {
+ val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException>
+ receiver.onError(SatelliteException(1 /* unused */))
+ null
+ }
+ .`when`(satelliteManager)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ any(),
+ any<OutcomeReceiver<Boolean, SatelliteException>>()
+ )
+
+ val latest by collectLastValue(underTest.isSatelliteAllowedForCurrentLocation)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun satelliteNotSupported_listenersAreNotRegistered() =
+ testScope.runTest {
+ setupDefaultRepo()
+ // GIVEN satellite is not supported
+ setUpRepo(
+ uptime = MIN_UPTIME,
+ satMan = satelliteManager,
+ satelliteSupported = false,
+ )
+
+ // WHEN data is requested from the repo
+ val connectionState by collectLastValue(underTest.connectionState)
+ val signalStrength by collectLastValue(underTest.signalStrength)
+
+ // THEN the manager is not asked for the information, and default values are returned
+ verify(satelliteManager, never()).registerForSatelliteModemStateChanged(any(), any())
+ verify(satelliteManager, never()).registerForNtnSignalStrengthChanged(any(), any())
+ }
+
+ @Test
+ fun repoDoesNotCheckForSupportUntilMinUptime() =
+ testScope.runTest {
+ // GIVEN we init 100ms after sysui starts up
+ setUpRepo(
+ uptime = 100,
+ satMan = satelliteManager,
+ satelliteSupported = true,
+ )
+
+ // WHEN data is requested
+ val connectionState by collectLastValue(underTest.connectionState)
+ val signalStrength by collectLastValue(underTest.signalStrength)
+
+ // THEN we have not yet talked to satellite manager, since we are well before MIN_UPTIME
+ Mockito.verifyZeroInteractions(satelliteManager)
+
+ // WHEN enough time has passed
+ systemClock.advanceTime(MIN_UPTIME)
+ runCurrent()
+
+ // THEN we finally register with the satellite manager
+ verify(satelliteManager).registerForSatelliteModemStateChanged(any(), any())
+ }
+
+ private fun setUpRepo(
+ uptime: Long = MIN_UPTIME,
+ satMan: SatelliteManager? = satelliteManager,
+ satelliteSupported: Boolean = true,
+ ) {
+ doAnswer {
+ val callback: OutcomeReceiver<Boolean, SatelliteException> =
+ it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException>
+ callback.onResult(satelliteSupported)
+ }
+ .whenever(satelliteManager)
+ .requestIsSatelliteSupported(any(), any())
+
+ systemClock.setUptimeMillis(Process.getStartUptimeMillis() + uptime)
+
+ underTest =
+ DeviceBasedSatelliteRepositoryImpl(
+ if (satMan != null) Optional.of(satMan) else Optional.empty(),
+ dispatcher,
+ testScope.backgroundScope,
+ systemClock,
+ )
+ }
+
+ // Set system time to MIN_UPTIME and create a repo with satellite supported
+ private fun setupDefaultRepo() {
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
new file mode 100644
index 000000000000..5fa2d33c9de0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.data.prod
+
+import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState.Off
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeDeviceBasedSatelliteRepository() : DeviceBasedSatelliteRepository {
+ override val connectionState = MutableStateFlow(Off)
+
+ override val signalStrength = MutableStateFlow(0)
+
+ override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
new file mode 100644
index 000000000000..e010b8672580
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG
+import com.android.systemui.SysuiTestCase
+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.shared.model.SatelliteConnectionState
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+
+@SmallTest
+class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: DeviceBasedSatelliteInteractor
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+
+ private val iconsInteractor =
+ FakeMobileIconsInteractor(
+ FakeMobileMappingsProxy(),
+ mock(),
+ )
+
+ private val repo = FakeDeviceBasedSatelliteRepository()
+
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ underTest =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ iconsInteractor,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun isSatelliteAllowed_falseWhenNotAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isSatelliteAllowed)
+
+ // WHEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = false
+
+ // THEN the interactor returns false due to the flag value
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSatelliteAllowed_trueWhenAllowed() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isSatelliteAllowed)
+
+ // WHEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // THEN the interactor returns false due to the flag value
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isSatelliteAllowed_offWhenFlagIsOff() =
+ testScope.runTest {
+ // GIVEN feature is disabled
+ mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ // Remake the interactor so the flag is read
+ underTest =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ iconsInteractor,
+ testScope.backgroundScope,
+ )
+
+ val latest by collectLastValue(underTest.isSatelliteAllowed)
+
+ // WHEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // THEN the interactor returns false due to the flag value
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun connectionState_matchesRepositoryValue() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.connectionState)
+
+ // Off
+ repo.connectionState.value = SatelliteConnectionState.Off
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+
+ // On
+ repo.connectionState.value = SatelliteConnectionState.On
+ assertThat(latest).isEqualTo(SatelliteConnectionState.On)
+
+ // Connected
+ repo.connectionState.value = SatelliteConnectionState.Connected
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Connected)
+
+ // Unknown
+ repo.connectionState.value = SatelliteConnectionState.Unknown
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Unknown)
+ }
+
+ @Test
+ fun connectionState_offWhenFeatureIsDisabled() =
+ testScope.runTest {
+ // GIVEN the flag is disabled
+ mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ // Remake the interactor so the flag is read
+ underTest =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ iconsInteractor,
+ testScope.backgroundScope,
+ )
+
+ val latest by collectLastValue(underTest.connectionState)
+
+ // THEN the state is always Off, regardless of status in system_server
+
+ // Off
+ repo.connectionState.value = SatelliteConnectionState.Off
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+
+ // On
+ repo.connectionState.value = SatelliteConnectionState.On
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+
+ // Connected
+ repo.connectionState.value = SatelliteConnectionState.Connected
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+
+ // Unknown
+ repo.connectionState.value = SatelliteConnectionState.Unknown
+ assertThat(latest).isEqualTo(SatelliteConnectionState.Off)
+ }
+
+ @Test
+ fun signalStrength_matchesRepo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalStrength)
+
+ repo.signalStrength.value = 1
+ assertThat(latest).isEqualTo(1)
+
+ repo.signalStrength.value = 2
+ assertThat(latest).isEqualTo(2)
+
+ repo.signalStrength.value = 3
+ assertThat(latest).isEqualTo(3)
+
+ repo.signalStrength.value = 4
+ assertThat(latest).isEqualTo(4)
+ }
+
+ @Test
+ fun signalStrength_zeroWhenDisabled() =
+ testScope.runTest {
+ // GIVEN the flag is enabled
+ mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ // Remake the interactor so the flag is read
+ underTest =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ iconsInteractor,
+ testScope.backgroundScope,
+ )
+
+ val latest by collectLastValue(underTest.signalStrength)
+
+ // THEN the value is always 0, regardless of what the system says
+ repo.signalStrength.value = 1
+ assertThat(latest).isEqualTo(0)
+
+ repo.signalStrength.value = 2
+ assertThat(latest).isEqualTo(0)
+
+ repo.signalStrength.value = 3
+ assertThat(latest).isEqualTo(0)
+
+ repo.signalStrength.value = 4
+ assertThat(latest).isEqualTo(0)
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_twoConnectionsOos_yes() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 2 connections
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+
+ // WHEN all of the connections are OOS
+ i1.isInService.value = false
+ i2.isInService.value = false
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_oneConnectionOos_yes() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ // WHEN all of the connections are OOS
+ i1.isInService.value = false
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_oneConnectionInService_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 1 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ // WHEN all of the connections are NOT OOS
+ i1.isInService.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_twoConnectionsOneInService_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 2 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2)
+
+ // WHEN at least 1 connection is NOT OOS.
+ i1.isInService.value = false
+ i2.isInService.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_twoConnectionsInService_no() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN, 2 connection
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ // WHEN all connections are NOT OOS.
+ i1.isInService.value = true
+ i2.isInService.value = true
+
+ // THEN the value is propagated to this interactor
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun areAllConnectionsOutOfService_falseWhenFlagIsOff() =
+ testScope.runTest {
+ // GIVEN the flag is disabled
+ mSetFlagsRule.disableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ // Remake the interactor so the flag is read
+ underTest =
+ DeviceBasedSatelliteInteractor(
+ repo,
+ iconsInteractor,
+ testScope.backgroundScope,
+ )
+
+ val latest by collectLastValue(underTest.areAllConnectionsOutOfService)
+
+ // GIVEN a condition that should return true (all conections OOS)
+
+ val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+ val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(1)
+
+ i1.isInService.value = true
+ i2.isInService.value = true
+
+ // THEN the value is still false, because the flag is off
+ assertThat(latest).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 2f79955c4f5b..a5c766d82d00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.policy;
import static android.os.BatteryManager.EXTRA_PRESENT;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -59,6 +61,7 @@ import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -286,6 +289,24 @@ public class BatteryControllerTest extends SysuiTestCase {
Assert.assertFalse(mBatteryController.isIncompatibleCharging());
}
+ @Test
+ public void callbackRemovedWhileDispatching_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ BatteryStateChangeCallback callback = new BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ if (remove.get()) {
+ mBatteryController.removeCallback(this);
+ }
+ }
+ };
+ mBatteryController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mBatteryController.addCallback(new BatteryStateChangeCallback() {});
+ remove.set(true);
+ mBatteryController.fireBatteryLevelChanged();
+ }
+
private void setupIncompatibleCharging() {
final List<UsbPort> usbPorts = new ArrayList<>();
usbPorts.add(mUsbPort);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index b8e4306a319d..68c1b8d3f44f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -127,6 +127,25 @@ public class CastControllerImplTest extends SysuiTestCase {
}
}
+ /** Regression test for b/317700495 */
+ @Test
+ public void removeCallbackWhileIterating_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ Callback callback = new Callback() {
+ @Override
+ public void onCastDevicesChanged() {
+ if (remove.get()) {
+ mController.removeCallback(this);
+ }
+ }
+ };
+ mController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mController.addCallback(() -> {});
+ remove.set(true);
+ mController.fireOnCastDevicesChanged();
+ }
+
@Test
public void hasConnectedCastDevice_connected() {
CastController.CastDevice castDevice = new CastController.CastDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index db0029af4ee2..777fa2871a64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -26,6 +26,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import java.util.concurrent.Executor
@@ -128,11 +130,42 @@ class FlashlightControllerImplTest : SysuiTestCase() {
verify(cameraManager).setTorchMode(id, enable)
}
+ @Test
+ fun testCallbackRemovedWhileDispatching_doesntCrash() {
+ injectCamera()
+ var remove = false
+ val callback = object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+
+ override fun onFlashlightError() {}
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+ }
+ controller.addCallback(callback)
+ controller.addCallback(object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {}
+
+ override fun onFlashlightError() {}
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+ })
+ backgroundExecutor.runAllReady()
+
+ val captor = argumentCaptor<CameraManager.TorchCallback>()
+ verify(cameraManager).registerTorchCallback(any(), capture(captor))
+ remove = true
+ captor.value.onTorchModeChanged(ID, true)
+ }
+
private fun injectCamera(
flash: Boolean = true,
facing: Int = CameraCharacteristics.LENS_FACING_BACK
): String {
- val cameraID = "ID"
+ val cameraID = ID
val camera = CameraCharacteristics(CameraMetadataNative().apply {
set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
set(CameraCharacteristics.LENS_FACING, facing)
@@ -141,4 +174,8 @@ class FlashlightControllerImplTest : SysuiTestCase() {
`when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
return cameraID
}
+
+ companion object {
+ private const val ID = "ID"
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
index 89989ce81dd6..b03edaf8ebd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
@@ -27,6 +27,7 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -130,4 +131,61 @@ class SafetyControllerTest : SysuiTestCase() {
controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
verify(listener, never()).onSafetyCenterEnableChanged(true)
}
+
+ @Test
+ fun listenerRemovedWhileDispatching_doesNotCrash() {
+ var remove = false
+ val callback = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.addCallback {}
+
+ remove = true
+
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+ }
+
+ @Test
+ fun listenerRemovedWhileDispatching_otherCallbacksCalled() {
+ var remove = false
+ var called = false
+
+ val callback1 = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ if (remove) {
+ controller.removeCallback(this)
+ }
+ }
+ }
+
+ val callback2 = object : SafetyController.Listener {
+ override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+ // When the first callback is removed, we track if this is called
+ if (remove) {
+ called = true
+ }
+ }
+ }
+
+ controller.addCallback(callback1)
+ controller.addCallback(callback2)
+
+ remove = true
+
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+
+ assertThat(called).isTrue()
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index c35bc69bf15b..1dab84eb6e6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -63,6 +63,7 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -217,6 +218,28 @@ public class SecurityControllerTest extends SysuiTestCase {
), any(NetworkCallback.class));
}
+ @Test
+ public void testRemoveCallbackWhileDispatch_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ SecurityController.SecurityControllerCallback callback =
+ new SecurityController.SecurityControllerCallback() {
+ @Override
+ public void onStateChanged() {
+ if (remove.get()) {
+ mSecurityController.removeCallback(this);
+ }
+ }
+ };
+ mSecurityController.addCallback(callback);
+ // Add another callback so the iteration continues
+ mSecurityController.addCallback(() -> {});
+ mBgExecutor.runAllReady();
+ remove.set(true);
+
+ mSecurityController.onUserSwitched(10);
+ mBgExecutor.runAllReady();
+ }
+
/**
* refresh CA certs by sending a user unlocked broadcast for the desired user
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 6825f650c421..f1a2c281595d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -47,6 +47,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@SmallTest
@@ -174,4 +175,26 @@ public class ZenModeControllerImplTest extends SysuiTestCase {
}
}
+
+ @Test
+ public void testCallbackRemovedWhileDispatching_doesntCrash() {
+ final AtomicBoolean remove = new AtomicBoolean(false);
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+ TestableLooper.get(this).processAllMessages();
+ final ZenModeController.Callback callback = new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ if (remove.get()) {
+ mController.removeCallback(this);
+ }
+ }
+ };
+ mController.addCallback(callback);
+ mController.addCallback(new ZenModeController.Callback() {});
+
+ remove.set(true);
+
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
+ TestableLooper.get(this).processAllMessages();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 4e61b89b9c3e..2bc05fcc8166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.unfold.progress
+import android.os.Handler
+import android.os.HandlerThread
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -24,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.utils.os.FakeHandler
import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@@ -93,4 +96,13 @@ class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() {
listener.assertNotStarted()
}
+
+ @Test
+ fun addCallback_fromBackgroundThread_succeeds() = runTest {
+ val bgHandler = Handler(HandlerThread("TestBgThread").apply { start() }.looper)
+ bgHandler.runWithScissors({ progressProvider.addCallback(listener) }, 5000L)
+
+ wrappedProgressProvider.onTransitionStarted()
+ listener.assertStarted()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
index db0139c9b0d1..55c49ee4360d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -41,6 +42,7 @@ public class PersistentConnectionManagerTest extends SysuiTestCase {
private static final int MAX_RETRIES = 5;
private static final int RETRY_DELAY_MS = 1000;
private static final int CONNECTION_MIN_DURATION_MS = 5000;
+ private static final String DUMPSYS_NAME = "dumpsys_name";
private FakeSystemClock mFakeClock = new FakeSystemClock();
private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
@@ -49,8 +51,14 @@ public class PersistentConnectionManagerTest extends SysuiTestCase {
private ObservableServiceConnection<Proxy> mConnection;
@Mock
+ private ObservableServiceConnection.Callback<Proxy> mConnectionCallback;
+
+ @Mock
private Observer mObserver;
+ @Mock
+ private DumpManager mDumpManager;
+
private static class Proxy {
}
@@ -63,6 +71,8 @@ public class PersistentConnectionManagerTest extends SysuiTestCase {
mConnectionManager = new PersistentConnectionManager<>(
mFakeClock,
mFakeExecutor,
+ mDumpManager,
+ DUMPSYS_NAME,
mConnection,
MAX_RETRIES,
RETRY_DELAY_MS,
@@ -154,4 +164,16 @@ public class PersistentConnectionManagerTest extends SysuiTestCase {
callbackCaptor.getValue().onSourceChanged();
verify(mConnection).bind();
}
+
+ @Test
+ public void testAddConnectionCallback() {
+ mConnectionManager.addConnectionCallback(mConnectionCallback);
+ verify(mConnection).addCallback(mConnectionCallback);
+ }
+
+ @Test
+ public void testRemoveConnectionCallback() {
+ mConnectionManager.removeConnectionCallback(mConnectionCallback);
+ verify(mConnection).removeCallback(mConnectionCallback);
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 3fdeb302dc34..3b5ff38e3663 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
+import android.util.Size
import com.android.systemui.biometrics.shared.model.DisplayRotation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,6 +30,9 @@ class FakeDisplayStateRepository : DisplayStateRepository {
private val _currentRotation = MutableStateFlow<DisplayRotation>(DisplayRotation.ROTATION_0)
override val currentRotation: StateFlow<DisplayRotation> = _currentRotation.asStateFlow()
+ private val _currentDisplaySize = MutableStateFlow<Size>(Size(0, 0))
+ override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow()
+
override val isReverseDefaultRotation = false
fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
@@ -38,4 +42,8 @@ class FakeDisplayStateRepository : DisplayStateRepository {
fun setCurrentRotation(currentRotation: DisplayRotation) {
_currentRotation.value = currentRotation
}
+
+ fun setCurrentDisplaySize(size: Size) {
+ _currentDisplaySize.value = size
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 51ce9f00a709..77f501f550d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.data.repository
+import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -28,6 +29,10 @@ class FakeFacePropertyRepository : FacePropertyRepository {
private val lockoutModesForUser = mutableMapOf<Int, LockoutMode>()
+ private val faceSensorLocation = MutableStateFlow<Point?>(null)
+ override val sensorLocation: StateFlow<Point?>
+ get() = faceSensorLocation
+
fun setLockoutMode(userId: Int, mode: LockoutMode) {
lockoutModesForUser[userId] = mode
}
@@ -38,4 +43,8 @@ class FakeFacePropertyRepository : FacePropertyRepository {
fun setSensorInfo(value: FaceSensorInfo?) {
faceSensorInfo.value = value
}
+
+ fun setSensorLocation(value: Point?) {
+ faceSensorLocation.value = value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ab1b6c11e02..3ea3ccfb2909 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -16,8 +16,24 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeCommunalMediaRepository(
- override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : CommunalMediaRepository
+class FakeCommunalMediaRepository : CommunalMediaRepository {
+ private val _mediaModel = MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ fun mediaActive(timestamp: Long = 0L) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = timestamp,
+ )
+ }
+
+ fun mediaInactive() {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index c85c27e277b4..e82cae45c8f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -52,4 +53,12 @@ class FakeCommunalRepository(
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
+
+ private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ override val isCtaTileInViewModeVisible: Flow<Boolean> =
+ _isCtaTileInViewModeVisible.asStateFlow()
+
+ override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+ _isCtaTileInViewModeVisible.value = isVisible
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c6f12e2014b3..397dc1a464bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,15 +1,38 @@
package com.android.systemui.communal.data.repository
+import android.content.ComponentName
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
/** Fake implementation of [CommunalWidgetRepository] */
-class FakeCommunalWidgetRepository : CommunalWidgetRepository {
+class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
+ CommunalWidgetRepository {
private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+ private val _widgetAdded = MutableSharedFlow<Int>()
+ val widgetAdded: Flow<Int> = _widgetAdded
+
+ private var nextWidgetId = 1
fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
_communalWidgets.value = inventory
}
+
+ override fun addWidget(
+ provider: ComponentName,
+ priority: Int,
+ configureWidget: suspend (id: Int) -> Boolean
+ ) {
+ coroutineScope.launch {
+ val id = nextWidgetId++
+ if (configureWidget.invoke(id)) {
+ _widgetAdded.emit(id)
+ }
+ }
+ }
}
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
index faacce64b2e4..eb287ee522c0 100644
--- 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
@@ -36,7 +36,8 @@ object CommunalInteractorFactory {
fun create(
testScope: TestScope = TestScope(),
communalRepository: FakeCommunalRepository = FakeCommunalRepository(),
- widgetRepository: FakeCommunalWidgetRepository = FakeCommunalWidgetRepository(),
+ widgetRepository: FakeCommunalWidgetRepository =
+ FakeCommunalWidgetRepository(testScope.backgroundScope),
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
index 788e3aa9c41a..1ffc9f4e30b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/FakeStatusBarNotificationsDataLayerModule.kt
@@ -15,8 +15,6 @@
*/
package com.android.systemui.statusbar.notification.data
-import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardStateRepositoryModule
import dagger.Module
-@Module(includes = [FakeNotificationsKeyguardStateRepositoryModule::class])
-object FakeStatusBarNotificationsDataLayerModule
+@Module(includes = []) object FakeStatusBarNotificationsDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
deleted file mode 100644
index 5d3cb4db9c7e..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeNotificationsKeyguardViewStateRepository.kt
+++ /dev/null
@@ -1,49 +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.statusbar.notification.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import dagger.Binds
-import dagger.Module
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-@SysUISingleton
-class FakeNotificationsKeyguardViewStateRepository @Inject constructor() :
- NotificationsKeyguardViewStateRepository {
- private val _notificationsFullyHidden = MutableStateFlow(false)
- override val areNotificationsFullyHidden: Flow<Boolean> = _notificationsFullyHidden
-
- private val _isPulseExpanding = MutableStateFlow(false)
- override val isPulseExpanding: Flow<Boolean> = _isPulseExpanding
-
- fun setNotificationsFullyHidden(fullyHidden: Boolean) {
- _notificationsFullyHidden.value = fullyHidden
- }
-
- fun setPulseExpanding(expanding: Boolean) {
- _isPulseExpanding.value = expanding
- }
-}
-
-@Module
-interface FakeNotificationsKeyguardStateRepositoryModule {
- @Binds
- fun bindFake(
- fake: FakeNotificationsKeyguardViewStateRepository
- ): NotificationsKeyguardViewStateRepository
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
index f2b9da413c22..df7fd94d19b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryKosmos.kt
@@ -18,7 +18,5 @@ package com.android.systemui.statusbar.notification.data.repository
import com.android.systemui.kosmos.Kosmos
-var Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
- Kosmos.Fixture { fakeNotificationsKeyguardViewStateRepository }
-val Kosmos.fakeNotificationsKeyguardViewStateRepository by
- Kosmos.Fixture { FakeNotificationsKeyguardViewStateRepository() }
+val Kosmos.notificationsKeyguardViewStateRepository: NotificationsKeyguardViewStateRepository by
+ Kosmos.Fixture { NotificationsKeyguardViewStateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
index 432464e86c3f..61a38b864c40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
@@ -18,13 +18,11 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
val Kosmos.notificationsKeyguardInteractor by Fixture {
NotificationsKeyguardInteractor(
repository = notificationsKeyguardViewStateRepository,
- backgroundDispatcher = testDispatcher,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
index 9bdf3d5d5307..fdce147d229b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
@@ -24,12 +24,16 @@ import com.android.systemui.unfold.dagger.UnfoldMain
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.util.Collections.synchronizedMap
/**
* [UnfoldTransitionProgressProvider] that forwards all progress to the main thread handler.
*
* This is needed when progress are calculated in the background, but some listeners need the
* callbacks in the main thread.
+ *
+ * Note that this class assumes that the root provider has thread safe callback registration, as
+ * they might be called from any thread.
*/
class MainThreadUnfoldTransitionProgressProvider
@AssistedInject
@@ -38,27 +42,20 @@ constructor(
@Assisted private val rootProvider: UnfoldTransitionProgressProvider
) : UnfoldTransitionProgressProvider {
- private val listenerMap = mutableMapOf<TransitionProgressListener, TransitionProgressListener>()
+ private val listenerMap: MutableMap<TransitionProgressListener, TransitionProgressListener> =
+ synchronizedMap(mutableMapOf())
override fun addCallback(listener: TransitionProgressListener) {
- assertMainThread()
val proxy = TransitionProgressListerProxy(listener)
rootProvider.addCallback(proxy)
listenerMap[listener] = proxy
}
override fun removeCallback(listener: TransitionProgressListener) {
- assertMainThread()
val proxy = listenerMap.remove(listener) ?: return
rootProvider.removeCallback(proxy)
}
- private fun assertMainThread() {
- check(mainHandler.looper.isCurrentThread) {
- "Should be called from the main thread, but this is ${Thread.currentThread()}"
- }
- }
-
override fun destroy() {
rootProvider.destroy()
}
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
index ed0340b11229..b44a153ae48c 100644
--- a/packages/overlays/NoCutoutOverlay/res/values/config.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -20,10 +20,17 @@
black in software (to avoid aliasing or emulate a cutout that is not physically existent).
-->
<bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+ <!-- Whether the display cutout region of the secondary built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillSecondaryBuiltInDisplayCutout">false</bool>
<!-- If true, and there is a cutout on the main built in display, the cutout will be masked
by shrinking the display such that it does not overlap the cutout area. -->
<bool name="config_maskMainBuiltInDisplayCutout">true</bool>
+ <!-- If true, and there is a cutout on the secondary built in display, the cutout will be masked
+ by shrinking the display such that it does not overlap the cutout area. -->
+ <bool name="config_maskSecondaryBuiltInDisplayCutout">true</bool>
<!-- Height of the status bar -->
<dimen name="status_bar_height_portrait">28dp</dimen>
diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
index 9cda2672eab0..cffcd0941df8 100644
--- a/proto/src/criticalevents/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -60,8 +60,11 @@ message CriticalEventProto {
JavaCrash java_crash = 5;
NativeCrash native_crash = 6;
SystemServerStarted system_server_started = 7;
+ InstallPackages install_packages = 8;
}
+ message InstallPackages {}
+
message SystemServerStarted {}
message Watchdog {
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a6ed8464128a..4b3772a7a54d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -284,7 +284,7 @@ class AssociationRequestsProcessor {
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
/* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
- timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+ /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
// Add role holder for association (if specified) and add new association to store.
maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index a7dbd1c15aec..e4cc1f8949b5 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -18,6 +18,8 @@ package com.android.server.companion;
import static android.os.UserHandle.getCallingUserId;
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -26,9 +28,11 @@ import android.companion.Flags;
import android.companion.datatransfer.SystemDataTransferRequest;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
@@ -58,6 +62,14 @@ class BackupRestoreProcessor {
@NonNull
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+ /**
+ * A structure that consists of a set of restored associations that are pending corresponding
+ * companion app to be installed.
+ */
+ @GuardedBy("mAssociationsPendingAppInstall")
+ private final PerUserAssociationSet mAssociationsPendingAppInstall =
+ new PerUserAssociationSet();
+
BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
@NonNull AssociationStoreImpl associationStore,
@NonNull PersistentDataStore persistentStore,
@@ -124,7 +136,7 @@ class BackupRestoreProcessor {
byte[] requestsPayload = new byte[buffer.getInt()];
buffer.get(requestsPayload);
List<SystemDataTransferRequest> restoredRequestsForUser =
- mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload);
+ mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
// Get a list of installed packages ahead of time.
List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
@@ -170,7 +182,7 @@ class BackupRestoreProcessor {
mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
null, null);
} else {
- // TODO(b/314992577): Check if package is installed before granting
+ addToPendingAppInstall(newAssociation);
}
// Re-map restored system data transfer requests to newly created associations
@@ -185,6 +197,30 @@ class BackupRestoreProcessor {
mService.persistStateForUser(userId);
}
+ void addToPendingAppInstall(@NonNull AssociationInfo association) {
+ association = (new AssociationInfo.Builder(association))
+ .setPending(true)
+ .build();
+
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).add(association);
+ }
+ }
+
+ void removeFromPendingAppInstall(@NonNull AssociationInfo association) {
+ synchronized (mAssociationsPendingAppInstall) {
+ mAssociationsPendingAppInstall.forUser(association.getUserId()).remove(association);
+ }
+ }
+
+ @NonNull
+ Set<AssociationInfo> getAssociationsPendingAppInstallForUser(@UserIdInt int userId) {
+ synchronized (mAssociationsPendingAppInstall) {
+ // Return a copy.
+ return new ArraySet<>(mAssociationsPendingAppInstall.forUser(userId));
+ }
+ }
+
/**
* Detects and handles collision between restored association and local association. Returns
* true if there has been a collision and false otherwise.
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 858887ae20c6..056ec895821d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -287,7 +287,9 @@ public class CompanionDeviceManagerService extends SystemService {
final Set<Integer> usersToPersistStateFor = new ArraySet<>();
for (AssociationInfo association : allAssociations) {
- if (!association.isRevoked()) {
+ if (association.isPending()) {
+ mBackupRestoreProcessor.addToPendingAppInstall(association);
+ } else if (!association.isRevoked()) {
activeAssociations.add(association);
} else if (maybeRemoveRoleHolderForAssociation(association)) {
// Nothing more to do here, but we'll need to persist all the associations to the
@@ -514,6 +516,9 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore.getAssociationsForUser(userId));
// ... and add the revoked (removed) association, that are yet to be permanently removed.
allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+ // ... and add the restored associations that are pending missing package installation.
+ allAssociations.addAll(mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId));
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
@@ -583,7 +588,19 @@ public class CompanionDeviceManagerService extends SystemService {
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
- // TODO(b/314992577): Retroactively grant roles for restored associations
+
+ Set<AssociationInfo> associationsPendingAppInstall = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo association : associationsPendingAppInstall) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ AssociationInfo newAssociation = new AssociationInfo.Builder(association)
+ .setPending(false)
+ .build();
+ mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+ null, null);
+ mBackupRestoreProcessor.removeFromPendingAppInstall(association);
+ }
}
// Revoke associations if the selfManaged companion device does not connect for 3 months.
@@ -1152,6 +1169,15 @@ public class CompanionDeviceManagerService extends SystemService {
usedIds.put(it.getId(), true);
}
+ // Some IDs may be reserved by associations that aren't stored yet due to missing
+ // package after a backup restoration. We don't want the ID to have been taken by
+ // another association by the time when it is activated from the package installation.
+ final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
+ .getAssociationsPendingAppInstallForUser(userId);
+ for (AssociationInfo it: pendingAssociations) {
+ usedIds.put(it.getId(), true);
+ }
+
// Second: collect all IDs that have been previously used for this package (and user).
final Set<Integer> previouslyUsedIds =
getPreviouslyUsedIdsForPackageLocked(userId, packageName);
@@ -1718,7 +1744,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index dbaf7e85b7fa..1ebe65c6aa5f 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -189,6 +189,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_ATTR_REVOKED = "revoked";
+ private static final String XML_ATTR_PENDING = "pending";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
@@ -464,8 +465,8 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage, tag,
MacAddress.fromString(deviceAddress), null, profile, null,
- /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
- Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
+ /* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
+ timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -496,6 +497,7 @@ final class PersistentDataStore {
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
+ final boolean pending = readBooleanAttribute(parser, XML_ATTR_PENDING, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
@@ -504,7 +506,7 @@ final class PersistentDataStore {
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
- timeApproved, lastTimeConnected, systemDataSyncFlags);
+ pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -558,8 +560,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
- writeBooleanAttribute(
- serializer, XML_ATTR_REVOKED, a.isRevoked());
+ writeBooleanAttribute(serializer, XML_ATTR_REVOKED, a.isRevoked());
+ writeBooleanAttribute(serializer, XML_ATTR_PENDING, a.isPending());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -603,14 +605,14 @@ final class PersistentDataStore {
@UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
- long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
+ boolean pending, long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
AssociationInfo associationInfo = null;
try {
// We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
// datastore is not guaranteed to be identical to the one from initial association.
associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
macAddress, displayName, profile, null, selfManaged, notify,
- revoked, timeApproved, lastTimeConnected, systemDataSyncFlags);
+ revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 8fe04547a9ec..51c5fd69cdf2 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -69,7 +69,6 @@ import java.util.concurrent.TimeoutException;
* <request
* association_id="1"
* data_type="1"
- * user_id="12"
* is_user_consented="true"
* </request>
* </requests>
@@ -86,7 +85,6 @@ public class SystemDataTransferRequestStore {
private static final String XML_ATTR_ASSOCIATION_ID = "association_id";
private static final String XML_ATTR_DATA_TYPE = "data_type";
- private static final String XML_ATTR_USER_ID = "user_id";
private static final String XML_ATTR_IS_USER_CONSENTED = "is_user_consented";
private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
@@ -169,12 +167,12 @@ public class SystemDataTransferRequestStore {
* Parse the byte array containing XML information of system data transfer requests into
* an array list of requests.
*/
- public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload) {
+ public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload, int userId) {
try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
- return readRequestsFromXml(parser);
+ return readRequestsFromXml(parser, userId);
} catch (XmlPullParserException | IOException e) {
Slog.e(LOG_TAG, "Error while reading requests file", e);
return new ArrayList<>();
@@ -226,7 +224,7 @@ public class SystemDataTransferRequestStore {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
- return readRequestsFromXml(parser);
+ return readRequestsFromXml(parser, userId);
} catch (XmlPullParserException | IOException e) {
Slog.e(LOG_TAG, "Error while reading requests file", e);
return new ArrayList<>();
@@ -236,7 +234,8 @@ public class SystemDataTransferRequestStore {
@NonNull
private ArrayList<SystemDataTransferRequest> readRequestsFromXml(
- @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ @NonNull TypedXmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
if (!isStartOfTag(parser, XML_TAG_REQUESTS)) {
throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS);
}
@@ -249,14 +248,15 @@ public class SystemDataTransferRequestStore {
break;
}
if (isStartOfTag(parser, XML_TAG_REQUEST)) {
- requests.add(readRequestFromXml(parser));
+ requests.add(readRequestFromXml(parser, userId));
}
}
return requests;
}
- private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser)
+ private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser,
+ int userId)
throws XmlPullParserException, IOException {
if (!isStartOfTag(parser, XML_TAG_REQUEST)) {
throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST);
@@ -264,7 +264,6 @@ public class SystemDataTransferRequestStore {
final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID);
final int dataType = readIntAttribute(parser, XML_ATTR_DATA_TYPE);
- final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
final boolean isUserConsented = readBooleanAttribute(parser, XML_ATTR_IS_USER_CONSENTED);
switch (dataType) {
@@ -321,7 +320,6 @@ public class SystemDataTransferRequestStore {
writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId());
writeIntAttribute(serializer, XML_ATTR_DATA_TYPE, request.getDataType());
- writeIntAttribute(serializer, XML_ATTR_USER_ID, request.getUserId());
writeBooleanAttribute(serializer, XML_ATTR_IS_USER_CONSENTED, request.isUserConsented());
serializer.endTag(null, XML_TAG_REQUEST);
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index d089b05238e4..2f9b6a56e316 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -55,9 +55,7 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
@GuardedBy("mCameras")
private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
- public VirtualCameraController() {
- connectVirtualCameraService();
- }
+ public VirtualCameraController() {}
@VisibleForTesting
VirtualCameraController(IVirtualCameraService virtualCameraService) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dd001ec7da27..a3fc3bf5ec72 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -203,6 +203,7 @@ java_library_static {
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
+ "policy_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 9f279b1ba3fe..329aac6f3a6a 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,6 +48,8 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +62,14 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
+import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* Performs a number of miscellaneous, non-system-critical actions
@@ -332,12 +337,12 @@ public class BootReceiver extends BroadcastReceiver {
*
* @param ctx Context
* @param tombstone path to the tombstone
- * @param proto whether the tombstone is stored as proto
+ * @param tombstoneProto the parsed proto tombstone
* @param processName the name of the process corresponding to the tombstone
* @param tmpFileLock the lock for reading/writing tmp files
*/
public static void addTombstoneToDropBox(
- Context ctx, File tombstone, boolean proto, String processName,
+ Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
ReentrantLock tmpFileLock) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
if (db == null) {
@@ -347,31 +352,33 @@ public class BootReceiver extends BroadcastReceiver {
// Check if we should rate limit and abort early if needed.
DropboxRateLimiter.RateLimitResult rateLimitResult =
- sDropboxRateLimiter.shouldRateLimit(
- proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
+ sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
if (rateLimitResult.shouldRateLimit()) return;
HashMap<String, Long> timestamps = readTimestamps();
try {
- if (proto) {
- if (recordFileTimestamp(tombstone, timestamps)) {
- // We need to attach the count indicating the number of dropped dropbox entries
- // due to rate limiting. Do this by enclosing the proto tombsstone in a
- // container proto that has the dropped entry count and the proto tombstone as
- // bytes (to avoid the complexity of reading and writing nested protos).
- tmpFileLock.lock();
- try {
- addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
- } finally {
- tmpFileLock.unlock();
- }
+ // Remove the memory data from the proto.
+ Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
+
+ final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
+
+ // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
+ String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
+ .append(rateLimitResult.createHeader())
+ .append(getTombstoneText(tombstoneBytes))
+ .toString();
+
+ // Add the tombstone without memory data to dropbox.
+ db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
+
+ // Add the tombstone proto to dropbox.
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ tmpFileLock.lock();
+ try {
+ addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
+ } finally {
+ tmpFileLock.unlock();
}
- } else {
- // Add the header indicating how many events have been dropped due to rate limiting.
- final String headers = getBootHeadersToLogAndUpdate()
- + rateLimitResult.createHeader();
- addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
- TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -380,11 +387,8 @@ public class BootReceiver extends BroadcastReceiver {
}
private static void addAugmentedProtoToDropbox(
- File tombstone, DropBoxManager db,
+ File tombstone, byte[] tombstoneBytes, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
- // Read the proto tombstone file as bytes.
- final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
-
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
@@ -417,6 +421,8 @@ public class BootReceiver extends BroadcastReceiver {
}
}
+ private static native String getTombstoneText(byte[] tombstoneBytes);
+
private static void addLastkToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String footers, String filename, int maxSize,
@@ -434,6 +440,31 @@ public class BootReceiver extends BroadcastReceiver {
addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
}
+ /** Removes memory information from the Tombstone proto. */
+ @VisibleForTesting
+ public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
+ Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
+ .clearMemoryMappings()
+ .clearThreads()
+ .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
+ .stream()
+ .map(BootReceiver::clearMemoryDump)
+ .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
+
+ if (tombstoneProto.hasSignalInfo()) {
+ tombstoneBuilder.setSignalInfo(
+ tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
+ }
+
+ return tombstoneBuilder.build();
+ }
+
+ private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
+ Map.Entry<Integer, TombstoneProtos.Thread> e) {
+ return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
+ e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
+ }
+
private static void addFileToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e289a56f5dc5..e923e30aa1c6 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -43,3 +43,6 @@ per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timez
per-file TelephonyRegistry.java = file:/telephony/OWNERS
per-file UiModeManagerService.java = file:/packages/SystemUI/OWNERS
per-file VcnManagementService.java = file:/services/core/java/com/android/server/vcn/OWNERS
+
+# SystemConfig
+per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
new file mode 100644
index 000000000000..70bd4b328b43
--- /dev/null
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Service that monitors for notifications with sensitive content and protects content from screen
+ * sharing
+ */
+public final class SensitiveContentProtectionManagerService extends SystemService {
+ private static final String TAG = "SensitiveContentProtect";
+ private static final boolean DEBUG = false;
+
+ @VisibleForTesting
+ NotificationListener mNotificationListener;
+ private @Nullable MediaProjectionManager mProjectionManager;
+ private @Nullable WindowManagerInternal mWindowManager;
+
+ private final MediaProjectionManager.Callback mProjectionCallback =
+ new MediaProjectionManager.Callback() {
+ @Override
+ public void onStart(MediaProjectionInfo info) {
+ if (DEBUG) Log.d(TAG, "onStart projection: " + info);
+ onProjectionStart();
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo info) {
+ if (DEBUG) Log.d(TAG, "onStop projection: " + info);
+ onProjectionEnd();
+ }
+ };
+
+ public SensitiveContentProtectionManagerService(@NonNull Context context) {
+ super(context);
+ mNotificationListener = new NotificationListener();
+ }
+
+ @Override
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase != SystemService.PHASE_BOOT_COMPLETED) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
+
+ init(getContext().getSystemService(MediaProjectionManager.class),
+ LocalServices.getService(WindowManagerInternal.class));
+ }
+
+ @VisibleForTesting
+ void init(MediaProjectionManager projectionManager,
+ WindowManagerInternal windowManager) {
+ if (DEBUG) Log.d(TAG, "init");
+
+ checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager");
+ checkNotNull(windowManager, "Failed to get valid WindowManagerInternal");
+
+ mProjectionManager = projectionManager;
+ mWindowManager = windowManager;
+
+ // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
+ // handler, delegate, and binder death recipient
+ mProjectionManager.addCallback(mProjectionCallback, new Handler(Looper.getMainLooper()));
+
+ try {
+ mNotificationListener.registerAsSystemService(getContext(),
+ new ComponentName(getContext(), NotificationListener.class),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
+ }
+
+ /** Cleanup any callbacks and listeners */
+ @VisibleForTesting
+ void onDestroy() {
+ if (mProjectionManager != null) {
+ mProjectionManager.removeCallback(mProjectionCallback);
+ }
+
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
+
+ if (mWindowManager != null) {
+ onProjectionEnd();
+ }
+ }
+
+ private void onProjectionStart() {
+ StatusBarNotification[] notifications;
+ try {
+ notifications = mNotificationListener.getActiveNotifications();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ notifications = new StatusBarNotification[0];
+ }
+
+ RankingMap rankingMap;
+ try {
+ rankingMap = mNotificationListener.getCurrentRanking();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ rankingMap = null;
+ }
+
+ // notify windowmanager of any currently posted sensitive content notifications
+ Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+ notifications,
+ rankingMap);
+
+ mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+ }
+
+ private void onProjectionEnd() {
+ // notify windowmanager to clear any sensitive notifications observed during projection
+ // session
+ mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ private Set<PackageInfo> getSensitivePackagesFromNotifications(
+ StatusBarNotification[] notifications, RankingMap rankingMap) {
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return Collections.emptySet();
+ }
+
+ Set<PackageInfo> sensitivePackages = new ArraySet<>();
+ for (StatusBarNotification sbn : notifications) {
+ NotificationListenerService.Ranking ranking =
+ rankingMap.getRawRankingObject(sbn.getKey());
+ if (ranking != null && ranking.hasSensitiveContent()) {
+ PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ sensitivePackages.add(info);
+ }
+ }
+ return sensitivePackages;
+ }
+
+ // TODO(b/317251408): add trigger that updates on onNotificationPosted,
+ // onNotificationRankingUpdate and onListenerConnected
+ @VisibleForTesting
+ static class NotificationListener extends NotificationListenerService {}
+}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 40b29d7b09d5..3483c1a1404a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -315,6 +315,11 @@ public class SystemConfig {
private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
+ // These packages will be set as 'prevent disable', where they are no longer possible
+ // for the end user to disable via settings. This flag should only be used for packages
+ // which meet the 'force or keep enabled apps' policy.
+ private final ArrayList<String> mPreventUserDisablePackages = new ArrayList<>();
+
// Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
@@ -504,6 +509,10 @@ public class SystemConfig {
return mAppDataIsolationWhitelistedApps;
}
+ public @NonNull ArrayList<String> getPreventUserDisablePackages() {
+ return mPreventUserDisablePackages;
+ }
+
/**
* Gets map of packagesNames to userTypes, dictating on which user types each package should be
* initially installed, and then removes this map from SystemConfig.
@@ -1309,6 +1318,16 @@ public class SystemConfig {
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "prevent-disable": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mPreventUserDisablePackages.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "install-in-user-type": {
// NB: We allow any directory permission to declare install-in-user-type.
readInstallInUserType(parser,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index e7e721b3ec55..9db5d0a99480 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -176,6 +176,7 @@ public class SettingsToPropertiesMapper {
"system_performance",
"system_sw_touch",
"system_sw_usb",
+ "statsd",
"test_suites",
"text",
"threadnetwork",
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index cbcd8f5974ac..4d5bce559a91 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -29,6 +29,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -345,7 +346,8 @@ public final class FadeOutManager {
}
if (apc.getPlayerProxy() != null) {
applyVolumeShaperInternal(apc, piid, volShaper,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED, skipRamp,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT);
mFadedPlayers.put(piid, volShaper);
} else {
if (DEBUG) {
@@ -361,7 +363,8 @@ public final class FadeOutManager {
final AudioPlaybackConfiguration apc = players.get(piid);
if ((apc != null) && (apc.getPlayerProxy() != null)) {
applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
- VolumeShaper.Operation.REVERSE);
+ VolumeShaper.Operation.REVERSE, /* skipRamp= */ false,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
} else {
// this piid was in the list of faded players, but wasn't found
if (DEBUG) {
@@ -373,6 +376,7 @@ public final class FadeOutManager {
mFadedPlayers.clear();
}
+ @GuardedBy("mLock")
void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
@Nullable VolumeShaper.Configuration config) {
int piid = Integer.valueOf(apc.getPlayerInterfaceId());
@@ -385,10 +389,17 @@ public final class FadeOutManager {
return;
}
+ VolumeShaper.Operation operation = VolumeShaper.Operation.REVERSE;
+ if (config != null) {
+ // replace and join the volumeshapers with (possibly) in progress fade out operation
+ // for a smoother fade in
+ operation = new VolumeShaper.Operation.Builder()
+ .replace(mFadedPlayers.get(piid).getId(), /* join= */ true).build();
+ }
mFadedPlayers.remove(piid);
if (apc.getPlayerProxy() != null) {
- applyVolumeShaperInternal(apc, piid, config,
- config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+ applyVolumeShaperInternal(apc, piid, config, operation, /* skipRamp= */ false,
+ PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading in player piid:" + piid
@@ -397,6 +408,7 @@ public final class FadeOutManager {
}
}
+ @GuardedBy("mLock")
void clear() {
if (mFadedPlayers.size() > 0) {
if (DEBUG) {
@@ -413,21 +425,40 @@ public final class FadeOutManager {
}
private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
- VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+ VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation,
+ boolean skipRamp, String eventType) {
VolumeShaper.Configuration config = volShaperConfig;
// when operation is reverse, use the fade out volume shaper config instead
if (operation.equals(VolumeShaper.Operation.REVERSE)) {
config = mFadedPlayers.get(piid);
}
try {
- PlaybackActivityMonitor.sEventLogger.enqueue(
- (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
- .printLog(TAG));
+ logFadeEvent(apc, piid, volShaperConfig, operation, skipRamp, eventType);
apc.getPlayerProxy().applyVolumeShaper(config, operation);
} catch (Exception e) {
- Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
- + " operation:" + operation, e);
+ Slog.e(TAG, "Error " + eventType + " piid:" + piid + " uid:" + mUid, e);
}
}
+
+ private void logFadeEvent(AudioPlaybackConfiguration apc, int piid,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation,
+ boolean skipRamp, String eventType) {
+ if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT)) {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp, config, operation))
+ .printLog(TAG));
+ return;
+ }
+
+ if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_IN)) {
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new PlaybackActivityMonitor.FadeInEvent(apc, skipRamp, config, operation))
+ .printLog(TAG));
+ return;
+ }
+
+ PlaybackActivityMonitor.sEventLogger.enqueue(
+ (new EventLogger.StringEvent(eventType + " piid:" + piid)).printLog(TAG));
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index e69fbbd2a083..08da32e8831c 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -84,6 +84,8 @@ public final class PlaybackActivityMonitor
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
+ /*package*/ static final String EVENT_TYPE_FADE_OUT = "fading out";
+ /*package*/ static final String EVENT_TYPE_FADE_IN = "fading in";
// ducking settings for a "normal duck" at -14dB
private static final VolumeShaper.Configuration DUCK_VSHAPE =
@@ -1204,11 +1206,13 @@ public final class PlaybackActivityMonitor
return;
}
try {
- sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck))
- .printLog(TAG));
- apc.getPlayerProxy().applyVolumeShaper(
- mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
- skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ VolumeShaper.Configuration config =
+ mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE;
+ VolumeShaper.Operation operation =
+ skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED;
+ sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck, config,
+ operation)).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(config, operation);
mDuckedPlayers.add(piid);
} catch (Exception e) {
Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
@@ -1363,58 +1367,41 @@ public final class PlaybackActivityMonitor
}
}
- static final class FadeEvent extends EventLogger.Event {
+ private abstract static class VolumeShaperEvent extends EventLogger.Event {
private final int mPlayerIId;
- private final int mPlayerType;
+ private final boolean mSkipRamp;
private final int mClientUid;
private final int mClientPid;
+ private final int mPlayerType;
private final AudioAttributes mPlayerAttr;
- private final VolumeShaper.Configuration mVShaper;
- private final VolumeShaper.Operation mVOperation;
+ private final VolumeShaper.Configuration mConfig;
+ private final VolumeShaper.Operation mOperation;
- FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
- VolumeShaper.Operation vOperation) {
+ abstract String getVSAction();
+
+ VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
mPlayerIId = apc.getPlayerInterfaceId();
+ mSkipRamp = skipRamp;
mClientUid = apc.getClientUid();
mClientPid = apc.getClientPid();
mPlayerAttr = apc.getAudioAttributes();
mPlayerType = apc.getPlayerType();
- mVShaper = vShaper;
- mVOperation = vOperation;
+ mConfig = config;
+ mOperation = operation;
}
@Override
public String eventToString() {
- return "Fade Event:" + " player piid:" + mPlayerIId
+ return getVSAction()
+ + " player piid:" + mPlayerIId
+ " uid/pid:" + mClientUid + "/" + mClientPid
+ + " skip ramp:" + mSkipRamp
+ " player type:"
+ AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+ " attr:" + mPlayerAttr
- + " volume shaper:" + mVShaper
- + " volume operation:" + mVOperation;
- }
- }
-
- private abstract static class VolumeShaperEvent extends EventLogger.Event {
- private final int mPlayerIId;
- private final boolean mSkipRamp;
- private final int mClientUid;
- private final int mClientPid;
-
- abstract String getVSAction();
-
- VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
- mPlayerIId = apc.getPlayerInterfaceId();
- mSkipRamp = skipRamp;
- mClientUid = apc.getClientUid();
- mClientPid = apc.getClientPid();
- }
-
- @Override
- public String eventToString() {
- return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId)
- .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
- .append(" skip ramp:").append(mSkipRamp).toString();
+ + " config:" + mConfig
+ + " operation:" + mOperation;
}
}
@@ -1426,9 +1413,10 @@ public final class PlaybackActivityMonitor
return mUseStrongDuck ? "ducking (strong)" : "ducking";
}
- DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation)
{
- super(apc, skipRamp);
+ super(apc, skipRamp, config, operation);
mUseStrongDuck = useStrongDuck;
}
}
@@ -1436,11 +1424,24 @@ public final class PlaybackActivityMonitor
static final class FadeOutEvent extends VolumeShaperEvent {
@Override
String getVSAction() {
- return "fading out";
+ return EVENT_TYPE_FADE_OUT;
+ }
+
+ FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+ super(apc, skipRamp, config, operation);
+ }
+ }
+
+ static final class FadeInEvent extends VolumeShaperEvent {
+ @Override
+ String getVSAction() {
+ return EVENT_TYPE_FADE_IN;
}
- FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
- super(apc, skipRamp);
+ FadeInEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+ VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+ super(apc, skipRamp, config, operation);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index c72632fb367d..c2bc1e4f6be2 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -893,7 +893,7 @@ public class SoundDoseHelper {
if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
&& safeDevicesContains(device)) {
soundDose.updateAttenuation(
- AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
+ -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
(newIndex + 5) / 10,
device), device);
}
diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index 08143759fab4..816c3490d0a0 100644
--- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -29,6 +29,7 @@ import com.android.server.criticalevents.nano.CriticalEventLogStorageProto;
import com.android.server.criticalevents.nano.CriticalEventProto;
import com.android.server.criticalevents.nano.CriticalEventProto.AppNotResponding;
import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.criticalevents.nano.CriticalEventProto.InstallPackages;
import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash;
import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash;
import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted;
@@ -142,6 +143,13 @@ public class CriticalEventLog {
return System.currentTimeMillis();
}
+ /** Logs when one or more packages are installed. */
+ public void logInstallPackagesStarted() {
+ CriticalEventProto event = new CriticalEventProto();
+ event.setInstallPackages(new InstallPackages());
+ log(event);
+ }
+
/** Logs when system server started. */
public void logSystemServerStarted() {
CriticalEventProto event = new CriticalEventProto();
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 7cea9c422b4d..e54f30fa29f1 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -27,6 +27,7 @@ import android.view.DisplayAddress;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.config.layout.Layouts;
import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
@@ -68,12 +69,15 @@ class DeviceStateToLayoutMap {
private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
private final DisplayIdProducer mIdProducer;
+ private final boolean mIsPortInDisplayLayoutEnabled;
- DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
- this(idProducer, getConfigFile());
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags) {
+ this(idProducer, flags, getConfigFile());
}
- DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags,
+ File configFile) {
+ mIsPortInDisplayLayoutEnabled = flags.isPortInDisplayLayoutEnabled();
mIdProducer = idProducer;
loadLayoutsFromConfig(configFile);
createLayout(STATE_DEFAULT);
@@ -93,6 +97,7 @@ class DeviceStateToLayoutMap {
ipw.println("DeviceStateToLayoutMap:");
ipw.increaseIndent();
+ ipw.println("mIsPortInDisplayLayoutEnabled=" + mIsPortInDisplayLayoutEnabled);
ipw.println("Registered Layouts:");
for (int i = 0; i < mLayoutMap.size(); i++) {
ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
@@ -132,13 +137,15 @@ class DeviceStateToLayoutMap {
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
assert layout != null;
+ final DisplayAddress address = getDisplayAddressForLayoutDisplay(d);
+
int position = getPosition(d.getPosition());
BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
: DisplayAddress.fromPhysicalDisplayId(
leadDisplayPhysicalId.longValue());
layout.createDisplayLocked(
- DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
+ address,
d.isDefaultDisplay(),
d.isEnabled(),
d.getDisplayGroup(),
@@ -158,6 +165,20 @@ class DeviceStateToLayoutMap {
}
}
+ private DisplayAddress getDisplayAddressForLayoutDisplay(
+ @NonNull com.android.server.display.config.layout.Display display) {
+ BigInteger xmlAddress = display.getAddress_optional();
+ if (xmlAddress != null) {
+ return DisplayAddress.fromPhysicalDisplayId(xmlAddress.longValue());
+ }
+ if (!mIsPortInDisplayLayoutEnabled || display.getPort_optional() == null) {
+ throw new IllegalArgumentException(
+ "Must specify a display identifier in display layout configuration: " + display);
+ }
+ return DisplayAddress.fromPortAndModel((int) display.getPort_optional().longValue(),
+ /* model= */ null);
+ }
+
private int getPosition(@NonNull String position) {
int positionInt = POSITION_UNKNOWN;
if (FRONT_STRING.equals(position)) {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 9fcaa1e2af16..d50a43aa93d1 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -33,6 +33,7 @@ public final class DisplayBrightnessState {
private final float mSdrBrightness;
private final float mMaxBrightness;
+ private final float mMinBrightness;
private final BrightnessReason mBrightnessReason;
private final String mDisplayBrightnessStrategyName;
private final boolean mShouldUseAutoBrightness;
@@ -50,6 +51,7 @@ public final class DisplayBrightnessState {
mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
mIsSlowChange = builder.isSlowChange();
mMaxBrightness = builder.getMaxBrightness();
+ mMinBrightness = builder.getMinBrightness();
mCustomAnimationRate = builder.getCustomAnimationRate();
mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
}
@@ -105,6 +107,13 @@ public final class DisplayBrightnessState {
}
/**
+ * @return minimum allowed brightness
+ */
+ public float getMinBrightness() {
+ return mMinBrightness;
+ }
+
+ /**
* @return custom animation rate
*/
public float getCustomAnimationRate() {
@@ -131,6 +140,7 @@ public final class DisplayBrightnessState {
stringBuilder.append(getShouldUseAutoBrightness());
stringBuilder.append("\n isSlowChange:").append(mIsSlowChange);
stringBuilder.append("\n maxBrightness:").append(mMaxBrightness);
+ stringBuilder.append("\n minBrightness:").append(mMinBrightness);
stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
stringBuilder.append("\n shouldUpdateScreenBrightnessSetting:")
.append(mShouldUpdateScreenBrightnessSetting);
@@ -160,6 +170,7 @@ public final class DisplayBrightnessState {
&& mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
&& mIsSlowChange == otherState.isSlowChange()
&& mMaxBrightness == otherState.getMaxBrightness()
+ && mMinBrightness == otherState.getMinBrightness()
&& mCustomAnimationRate == otherState.getCustomAnimationRate()
&& mShouldUpdateScreenBrightnessSetting
== otherState.shouldUpdateScreenBrightnessSetting();
@@ -168,7 +179,8 @@ public final class DisplayBrightnessState {
@Override
public int hashCode() {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
- mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate,
+ mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
+ mCustomAnimationRate,
mShouldUpdateScreenBrightnessSetting);
}
@@ -190,6 +202,7 @@ public final class DisplayBrightnessState {
private boolean mShouldUseAutoBrightness;
private boolean mIsSlowChange;
private float mMaxBrightness;
+ private float mMinBrightness;
private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
private boolean mShouldUpdateScreenBrightnessSetting;
@@ -208,6 +221,7 @@ public final class DisplayBrightnessState {
builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
builder.setIsSlowChange(state.isSlowChange());
builder.setMaxBrightness(state.getMaxBrightness());
+ builder.setMinBrightness(state.getMinBrightness());
builder.setCustomAnimationRate(state.getCustomAnimationRate());
builder.setShouldUpdateScreenBrightnessSetting(
state.shouldUpdateScreenBrightnessSetting());
@@ -334,6 +348,20 @@ public final class DisplayBrightnessState {
return mMaxBrightness;
}
+ /**
+ * See {@link DisplayBrightnessState#getMinBrightness()}.
+ */
+ public Builder setMinBrightness(float minBrightness) {
+ this.mMinBrightness = minBrightness;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getMinBrightness()}.
+ */
+ public float getMinBrightness() {
+ return mMinBrightness;
+ }
/**
* See {@link DisplayBrightnessState#getCustomAnimationRate()}.
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index bd22e1d21dea..4c4cf6080965 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -567,7 +568,8 @@ public class DisplayDeviceConfig {
public static final int DEFAULT_LOW_REFRESH_RATE = 60;
- private static final float BRIGHTNESS_DEFAULT = 0.5f;
+ @VisibleForTesting
+ static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
private static final String DISPLAY_CONFIG_DIR = "displayconfig";
private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
@@ -597,8 +599,6 @@ public class DisplayDeviceConfig {
// so -2 is used instead
private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
- static final float NITS_INVALID = -1;
-
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -1031,11 +1031,12 @@ public class DisplayDeviceConfig {
/**
* Calculates the nits value for the specified backlight value if a mapping exists.
*
- * @return The mapped nits or {@link #NITS_INVALID} if no mapping exits.
+ * @return The mapped nits or {@link BrightnessMappingStrategy.INVALID_NITS} if no mapping
+ * exits.
*/
public float getNitsFromBacklight(float backlight) {
if (mBacklightToNitsSpline == null) {
- return NITS_INVALID;
+ return INVALID_NITS;
}
backlight = Math.max(backlight, mBacklightMinimum);
return mBacklightToNitsSpline.interpolate(backlight);
@@ -1061,7 +1062,7 @@ public class DisplayDeviceConfig {
float backlight = getBacklightFromBrightness(brightness);
float nits = getNitsFromBacklight(backlight);
- if (nits == NITS_INVALID) {
+ if (nits == INVALID_NITS) {
return PowerManager.BRIGHTNESS_INVALID;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 67e612d1fd99..6164154b1e63 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -129,7 +129,9 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
final DisplayDevice device = mDisplayDevices.get(i);
- if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if (address.equals(info.address)
+ || DisplayAddress.Physical.isPortMatch(address, info.address)) {
return device;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index bc3f9dd3cb8c..fbac924be283 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1615,6 +1615,10 @@ public final class DisplayManagerService extends SystemService {
if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
Slog.w(TAG, "Display created with home support but lacks "
+ "VIRTUAL_DISPLAY_FLAG_TRUSTED, ignoring the home support request.");
+ } else if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+ Slog.w(TAG, "Display created with home support but has "
+ + "VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, ignoring the home support "
+ + "request.");
} else {
mWindowManagerInternal.setHomeSupportedOnDisplay(displayUniqueId,
Display.TYPE_VIRTUAL, true);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7df61142475a..2d860c0cc673 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -573,10 +573,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mBrightnessClamperController = mInjector.getBrightnessClamperController(
mHandler, modeChangeCallback::run,
new BrightnessClamperController.DisplayDeviceData(
- mUniqueDisplayId,
- mThermalBrightnessThrottlingDataId,
- logicalDisplay.getPowerThrottlingDataIdLocked(),
- mDisplayDeviceConfig), mContext, flags);
+ mUniqueDisplayId,
+ mThermalBrightnessThrottlingDataId,
+ logicalDisplay.getPowerThrottlingDataIdLocked(),
+ mDisplayDeviceConfig), mContext, flags);
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
mAutomaticBrightnessStrategy =
@@ -1508,7 +1508,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
// we broadcast this change through setting.
final float unthrottledBrightnessState = brightnessState;
-
DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
brightnessState, slowChange);
@@ -1522,11 +1521,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (updateScreenBrightnessSetting) {
// Tell the rest of the system about the new brightness in case we had to change it
// for things like auto-brightness or high-brightness-mode. Note that we do this
- // only considering maxBrightness (ignroing brightness modifiers like low power or dim)
+ // only considering maxBrightness (ignoring brightness modifiers like low power or dim)
// so that the slider accurately represents the full possible range,
// even if they range changes what it means in absolute terms.
mDisplayBrightnessController.updateScreenBrightnessSetting(
- Math.min(unthrottledBrightnessState, clampedState.getMaxBrightness()));
+ MathUtils.constrain(unthrottledBrightnessState,
+ clampedState.getMinBrightness(), clampedState.getMaxBrightness()));
}
// The current brightness to use has been calculated at this point, and HbmController should
@@ -1935,8 +1935,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Nullable DisplayBrightnessState state) {
synchronized (mCachedBrightnessInfo) {
float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
- final float minBrightness = Math.min(
- mBrightnessRangeController.getCurrentBrightnessMin(), stateMax);
+ float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+ final float minBrightness = Math.max(stateMin, Math.min(
+ mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
final float maxBrightness = Math.min(
mBrightnessRangeController.getCurrentBrightnessMax(), stateMax);
boolean changed = false;
@@ -1962,7 +1963,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
mBrightnessClamperController.getBrightnessMaxReason());
-
return changed;
}
}
@@ -2880,6 +2880,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
(modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
mBrightnessClamperController.getBrightnessMaxReason(),
+ // TODO: (flc) add brightnessMinReason here too.
(modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
event.isRbcEnabled(),
(flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index bcf27b4e8f0a..90bad12869f4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -333,6 +333,8 @@ final class DisplayPowerState {
public void stop() {
mStopped = true;
mPhotonicModulator.interrupt();
+ mColorFadePrepared = false;
+ mColorFadeReady = true;
if (mColorFade != null) {
mAsyncDestroyExecutor.execute(mColorFade::destroy);
}
@@ -419,7 +421,8 @@ final class DisplayPowerState {
}
};
- private final Runnable mColorFadeDrawRunnable = new Runnable() {
+ @VisibleForTesting
+ final Runnable mColorFadeDrawRunnable = new Runnable() {
@Override
public void run() {
mColorFadeDrawPending = false;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 25576ce9efd6..3a6333099b77 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -19,6 +19,8 @@ package com.android.server.display;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.Mode.INVALID_MODE_ID;
+import static com.android.server.display.BrightnessMappingStrategy.INVALID_NITS;
+
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.content.Context;
@@ -956,8 +958,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
void handleHdrSdrNitsChanged(float displayNits, float sdrNits) {
final float newHdrSdrRatio;
- if (displayNits != DisplayDeviceConfig.NITS_INVALID
- && sdrNits != DisplayDeviceConfig.NITS_INVALID) {
+ if (displayNits != INVALID_NITS && sdrNits != INVALID_NITS) {
// Ensure the ratio stays >= 1.0f as values below that are nonsensical
newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits);
} else {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 115111a4afa3..2e8de31f2af1 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -205,7 +205,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
@NonNull Handler handler, DisplayManagerFlags flags) {
this(context, foldSettingProvider, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++), flags);
+ : sNextNonDefaultDisplayId++, flags), flags);
}
LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
@@ -1094,8 +1094,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final DisplayAddress address = displayLayout.getAddress();
final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
if (device == null) {
- Slog.w(TAG, "The display device (" + address + "), is not available"
- + " for the display state " + mDeviceState);
+ Slog.w(TAG, "applyLayoutLocked: The display device (" + address + "), is not "
+ + "available for the display state " + mDeviceState);
continue;
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index 8fe5f213d766..bc443a8167ab 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -46,8 +46,10 @@ public final class BrightnessReason {
public static final int MODIFIER_LOW_POWER = 0x2;
public static final int MODIFIER_HDR = 0x4;
public static final int MODIFIER_THROTTLED = 0x8;
+ public static final int MODIFIER_MIN_LUX = 0x10;
+ public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20;
public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
- | MODIFIER_THROTTLED;
+ | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -131,6 +133,12 @@ public final class BrightnessReason {
if ((mModifier & MODIFIER_THROTTLED) != 0) {
sb.append(" throttled");
}
+ if ((mModifier & MODIFIER_MIN_LUX) != 0) {
+ sb.append(" lux_lower_bound");
+ }
+ if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) {
+ sb.append(" user_min_pref");
+ }
int strlen = sb.length();
if (sb.charAt(strlen - 1) == '[') {
sb.setLength(strlen - 2);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 42ebc401335e..fab769e8bc4f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -30,6 +30,7 @@ import java.io.PrintWriter;
abstract class BrightnessClamper<T> {
protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
protected boolean mIsActive = false;
@NonNull
@@ -75,6 +76,5 @@ abstract class BrightnessClamper<T> {
THERMAL,
POWER,
BEDTIME_MODE,
- LUX,
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 01694ddee06a..2c02fc610a51 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -58,13 +58,14 @@ public class BrightnessClamperController {
private final Executor mExecutor;
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers;
- private final List<BrightnessModifier> mModifiers;
+ private final List<BrightnessStateModifier> mModifiers;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
+
private boolean mClamperApplied = false;
public BrightnessClamperController(Handler handler,
@@ -92,7 +93,7 @@ public class BrightnessClamperController {
mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
context);
- mModifiers = injector.getModifiers(context);
+ mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
start();
@@ -165,9 +166,10 @@ public class BrightnessClamperController {
* Used to dump ClampersController state.
*/
public void dump(PrintWriter writer) {
- writer.println("BrightnessClampersController:");
+ writer.println("BrightnessClamperController:");
writer.println(" mBrightnessCap: " + mBrightnessCap);
writer.println(" mClamperType: " + mClamperType);
+ writer.println(" mClamperApplied: " + mClamperApplied);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
mClampers.forEach(clamper -> clamper.dump(ipw));
mModifiers.forEach(modifier -> modifier.dump(ipw));
@@ -181,6 +183,7 @@ public class BrightnessClamperController {
mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
mOnPropertiesChangedListener);
mClampers.forEach(BrightnessClamper::stop);
+ mModifiers.forEach(BrightnessStateModifier::stop);
}
@@ -201,14 +204,14 @@ public class BrightnessClamperController {
customAnimationRate = minClamper.getCustomAnimationRate();
}
- if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+ if (mBrightnessCap != brightnessCap
+ || mClamperType != clamperType
|| mCustomAnimationRate != customAnimationRate) {
mBrightnessCap = brightnessCap;
mClamperType = clamperType;
mCustomAnimationRate = customAnimationRate;
mClamperChangeListenerExternal.onChanged();
}
-
}
private void start() {
@@ -248,16 +251,17 @@ public class BrightnessClamperController {
clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
clamperChangeListener, data));
}
- if (flags.isEvenDimmerEnabled()) {
- clampers.add(new BrightnessMinClamper(handler, clamperChangeListener, context));
- }
return clampers;
}
- List<BrightnessModifier> getModifiers(Context context) {
- List<BrightnessModifier> modifiers = new ArrayList<>();
+ List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
+ Handler handler, ClamperChangeListener listener) {
+ List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
+ if (flags.isEvenDimmerEnabled()) {
+ modifiers.add(new BrightnessLowLuxModifier(handler, listener, context));
+ }
return modifiers;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
new file mode 100644
index 000000000000..7f1f7a99e438
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManagerInternal;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.utils.DebugUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Class used to prevent the screen brightness dipping below a certain value, based on current
+ * lux conditions and user preferred minimum.
+ */
+public class BrightnessLowLuxModifier implements
+ BrightnessStateModifier {
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.BrightnessLowLuxModifier DEBUG && adb reboot'
+ private static final String TAG = "BrightnessLowLuxModifier";
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private final SettingsObserver mSettingsObserver;
+ private final ContentResolver mContentResolver;
+ private final Handler mHandler;
+ private final BrightnessClamperController.ClamperChangeListener mChangeListener;
+ protected float mSettingNitsLowerBound = PowerManager.BRIGHTNESS_MIN;
+ private int mReason;
+ private float mBrightnessLowerBound;
+ private boolean mIsActive;
+
+ @VisibleForTesting
+ BrightnessLowLuxModifier(Handler handler,
+ BrightnessClamperController.ClamperChangeListener listener, Context context) {
+ super();
+
+ mChangeListener = listener;
+ mHandler = handler;
+ mContentResolver = context.getContentResolver();
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mHandler.post(() -> {
+ start();
+ });
+ }
+
+ /**
+ * Calculates new lower bound for brightness range, based on whether the setting is active,
+ * the user defined min brightness setting, and current lux environment.
+ */
+ @VisibleForTesting
+ public void recalculateLowerBound() {
+ int userId = UserHandle.USER_CURRENT;
+ float settingNitsLowerBound = Settings.Secure.getFloatForUser(
+ mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
+ /* def= */ PowerManager.BRIGHTNESS_MIN, userId);
+
+ boolean isActive = Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED,
+ /* def= */ 0, userId) == 1;
+
+ // TODO: luxBasedNitsLowerBound = mMinNitsToLuxSpline(currentLux);
+ float luxBasedNitsLowerBound = 0.0f;
+
+ // TODO: final float nitsLowerBound = isActive ? Math.max(settingNitsLowerBound,
+ // luxBasedNitsLowerBound) : PowerManager.BRIGHTNESS_MIN;
+
+ final int reason = settingNitsLowerBound > luxBasedNitsLowerBound
+ ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
+ : BrightnessReason.MODIFIER_MIN_LUX;
+
+ // TODO: brightnessLowerBound = nitsToBrightnessSpline(nitsLowerBound);
+ final float brightnessLowerBound = PowerManager.BRIGHTNESS_MIN;
+
+ if (mBrightnessLowerBound != brightnessLowerBound
+ || mReason != reason
+ || mIsActive != isActive) {
+ mIsActive = isActive;
+ mReason = reason;
+ if (DEBUG) {
+ Slog.i(TAG, "isActive: " + isActive
+ + ", settingNitsLowerBound: " + settingNitsLowerBound
+ + ", lowerBound: " + brightnessLowerBound);
+ }
+ mBrightnessLowerBound = brightnessLowerBound;
+ mChangeListener.onChanged();
+ }
+ }
+
+ @VisibleForTesting
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ @VisibleForTesting
+ public int getBrightnessReason() {
+ return mReason;
+ }
+
+ @VisibleForTesting
+ public float getBrightnessLowerBound() {
+ return mBrightnessLowerBound;
+ }
+
+ void start() {
+ recalculateLowerBound();
+ }
+
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder) {
+ stateBuilder.setMinBrightness(mBrightnessLowerBound);
+ float boundedBrightness = Math.max(mBrightnessLowerBound, stateBuilder.getBrightness());
+ stateBuilder.setBrightness(boundedBrightness);
+
+ if (BrightnessSynchronizer.floatEquals(stateBuilder.getBrightness(),
+ mBrightnessLowerBound)) {
+ stateBuilder.getBrightnessReason().addModifier(mReason);
+ }
+ }
+
+ @Override
+ public void stop() {
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("BrightnessLowLuxModifier:");
+ pw.println(" mBrightnessLowerBound=" + mBrightnessLowerBound);
+ pw.println(" mIsActive=" + mIsActive);
+ pw.println(" mReason=" + mReason);
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
+ false, this);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
+ false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ recalculateLowerBound();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java
deleted file mode 100644
index 71efca12f91c..000000000000
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java
+++ /dev/null
@@ -1,137 +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.server.display.brightness.clamper;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.utils.DebugUtils;
-
-import java.io.PrintWriter;
-
-/**
- * Class used to prevent the screen brightness dipping below a certain value, based on current
- * lux conditions.
- */
-public class BrightnessMinClamper extends BrightnessClamper {
-
- // To enable these logs, run:
- // 'adb shell setprop persist.log.tag.BrightnessMinClamper DEBUG && adb reboot'
- private static final String TAG = "BrightnessMinClamper";
- private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
- private final SettingsObserver mSettingsObserver;
-
- ContentResolver mContentResolver;
- private float mNitsLowerBound;
-
- @VisibleForTesting
- BrightnessMinClamper(Handler handler,
- BrightnessClamperController.ClamperChangeListener listener, Context context) {
- super(handler, listener);
-
- mContentResolver = context.getContentResolver();
- mSettingsObserver = new SettingsObserver(mHandler);
- mHandler.post(() -> {
- start();
- });
- }
-
- private void recalculateLowerBound() {
- final int userId = UserHandle.USER_CURRENT;
- float settingNitsLowerBound = Settings.Secure.getFloatForUser(
- mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ PowerManager.BRIGHTNESS_MIN, userId);
-
- boolean isActive = Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 0, userId) == 1;
-
- // TODO: luxBasedNitsLowerBound = mMinNitsToLuxSpline(currentLux);
- float luxBasedNitsLowerBound = PowerManager.BRIGHTNESS_MIN;
- final float nitsLowerBound = Math.max(settingNitsLowerBound, luxBasedNitsLowerBound);
-
- if (mNitsLowerBound != nitsLowerBound || mIsActive != isActive) {
- mIsActive = isActive;
- mNitsLowerBound = nitsLowerBound;
- if (DEBUG) {
- Slog.i(TAG, "mIsActive: " + mIsActive);
- }
- // TODO: mBrightnessCap = nitsToBrightnessSpline(mNitsLowerBound);
- mChangeListener.onChanged();
- }
- }
-
- void start() {
- recalculateLowerBound();
- }
-
-
- @Override
- Type getType() {
- return Type.LUX;
- }
-
- @Override
- void onDeviceConfigChanged() {
- // TODO
- }
-
- @Override
- void onDisplayChanged(Object displayData) {
-
- }
-
- @Override
- void stop() {
- mContentResolver.unregisterContentObserver(mSettingsObserver);
- }
-
- @Override
- void dump(PrintWriter pw) {
- pw.println("BrightnessMinClamper:");
- pw.println(" mBrightnessCap=" + mBrightnessCap);
- pw.println(" mIsActive=" + mIsActive);
- pw.println(" mNitsLowerBound=" + mNitsLowerBound);
- super.dump(pw);
- }
-
- private final class SettingsObserver extends ContentObserver {
- SettingsObserver(Handler handler) {
- super(handler);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
- false, this);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
- false, this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- recalculateLowerBound();
- }
- }
-}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index 112e63dc62d4..be8fa5a0f0ce 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -26,7 +26,7 @@ import java.io.PrintWriter;
/**
* Modifies current brightness based on request
*/
-abstract class BrightnessModifier {
+abstract class BrightnessModifier implements BrightnessStateModifier {
private boolean mApplied = false;
@@ -37,7 +37,8 @@ abstract class BrightnessModifier {
abstract int getModifier();
- void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ @Override
+ public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
// If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
// as long as it is above the minimum threshold.
@@ -57,8 +58,14 @@ abstract class BrightnessModifier {
}
}
- void dump(PrintWriter pw) {
+ @Override
+ public void dump(PrintWriter pw) {
pw.println("BrightnessModifier:");
pw.println(" mApplied=" + mApplied);
}
+
+ @Override
+ public void stop() {
+ // do nothing
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
new file mode 100644
index 000000000000..441ba8f1a1fc
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+public interface BrightnessStateModifier {
+ /**
+ * Applies the changes to brightness state, by modifying properties of the brightness
+ * state builder.
+ * @param request
+ * @param stateBuilder
+ */
+ void apply(DisplayManagerInternal.DisplayPowerRequest request,
+ DisplayBrightnessState.Builder stateBuilder);
+
+ /**
+ * Prints contents of this brightness state modifier
+ * @param printWriter
+ */
+ void dump(PrintWriter printWriter);
+
+ /**
+ * Called when stopped. Listeners can be unregistered here.
+ */
+ void stop();
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 35991b356bba..be48eb437dfe 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -37,6 +37,9 @@ public class DisplayManagerFlags {
// 'adb shell setprop persist.log.tag.DisplayManagerFlags DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private final FlagState mPortInDisplayLayoutFlagState = new FlagState(
+ Flags.FLAG_ENABLE_PORT_IN_DISPLAY_LAYOUT,
+ Flags::enablePortInDisplayLayout);
private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
@@ -114,6 +117,13 @@ public class DisplayManagerFlags {
Flags::refreshRateVotingTelemetry
);
+ /**
+ * @return {@code true} if 'port' is allowed in display layout configuration file.
+ */
+ public boolean isPortInDisplayLayoutEnabled() {
+ return mPortInDisplayLayoutFlagState.isEnabled();
+ }
+
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
return mConnectedDisplayManagementFlagState.isEnabled();
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index e735282a496e..c9569cbf4b9a 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -3,6 +3,14 @@ package: "com.android.server.display.feature.flags"
# Important: Flags must be accessed through DisplayManagerFlags.
flag {
+ name: "enable_port_in_display_layout"
+ namespace: "display_manager"
+ description: "Allows refering to displays by port in display layout"
+ bug: "303058435"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_connected_display_management"
namespace: "display_manager"
description: "Feature flag for Connected Display management"
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 40cb3303adda..8a362f78a9a3 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -200,13 +200,7 @@ public class Layout {
* @return True if the specified address is used in this layout.
*/
public boolean contains(@NonNull DisplayAddress address) {
- final int size = mDisplays.size();
- for (int i = 0; i < size; i++) {
- if (address.equals(mDisplays.get(i).getAddress())) {
- return true;
- }
- }
- return false;
+ return getByAddress(address) != null;
}
/**
@@ -237,6 +231,9 @@ public class Layout {
if (address.equals(display.getAddress())) {
return display;
}
+ if (DisplayAddress.Physical.isPortMatch(address, display.getAddress())) {
+ return display;
+ }
}
return null;
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
new file mode 100644
index 000000000000..293464054fdc
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+/**
+ * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
+ * singleton in {@link InputMethodManagerService} since it stores information about all clients,
+ * still the current client will be defined per display.
+ *
+ * <p>
+ * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
+ * fields and methods will be moved out from IMMS and placed here:
+ * <ul>
+ * <li>mCurClient (ClientState)</li>
+ * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
+ * <li>mLastSwitchUserId</li>
+ * </ul>
+ * <p>
+ * Nested Classes (to move from IMMS):
+ * <ul>
+ * <li>ClientDeathRecipient</li>
+ * <li>ClientState<</li>
+ * </ul>
+ * <p>
+ * Methods to rewrite and/or extract from IMMS and move here:
+ * <ul>
+ * <li>addClient</li>
+ * <li>removeClient</li>
+ * <li>verifyClientAndPackageMatch</li>
+ * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
+ * <li>unbindCurrentClient</li>
+ * </ul>
+ */
+// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
+// class is finalized
+final class ClientController {
+
+ // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
+ @GuardedBy("ImfLock.class")
+ final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ ClientController(PackageManagerInternal packageManagerInternal) {
+ mPackageManagerInternal = packageManagerInternal;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void addClient(IInputMethodClientInvoker clientInvoker,
+ IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
+ int callerPid) {
+ // TODO: Optimize this linear search.
+ final int numClients = mClients.size();
+ for (int i = 0; i < numClients; ++i) {
+ final ClientState state = mClients.valueAt(i);
+ if (state.mUid == callerUid && state.mPid == callerPid
+ && state.mSelfReportedDisplayId == selfReportedDisplayId) {
+ throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
+ + "/displayId=" + selfReportedDisplayId + " is already registered");
+ }
+ }
+ try {
+ clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ // We cannot fully avoid race conditions where the client UID already lost the access to
+ // the given self-reported display ID, even if the client is not maliciously reporting
+ // a fake display ID. Unconditionally returning SecurityException just because the
+ // client doesn't pass display ID verification can cause many test failures hence not an
+ // option right now. At the same time
+ // context.getSystemService(InputMethodManager.class)
+ // is expected to return a valid non-null instance at any time if we do not choose to
+ // have the client crash. Thus we do not verify the display ID at all here. Instead we
+ // later check the display ID every time the client needs to interact with the specified
+ // display.
+ mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
+ callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+ }
+
+ @GuardedBy("ImfLock.class")
+ boolean verifyClientAndPackageMatch(
+ @NonNull IInputMethodClient client, @NonNull String packageName) {
+ ClientState cs = mClients.get(client.asBinder());
+ if (cs == null) {
+ throw new IllegalArgumentException("unknown client " + client.asBinder());
+ }
+ return InputMethodUtils.checkIfPackageBelongsToUid(
+ mPackageManagerInternal, cs.mUid, packageName);
+ }
+
+ static final class ClientState {
+ final IInputMethodClientInvoker mClient;
+ final IRemoteInputConnection mFallbackInputConnection;
+ final int mUid;
+ final int mPid;
+ final int mSelfReportedDisplayId;
+ final InputBinding mBinding;
+ final IBinder.DeathRecipient mClientDeathRecipient;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequested;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequestedForAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ InputMethodManagerService.SessionState mCurSession;
+
+ @GuardedBy("ImfLock.class")
+ SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+ new SparseArray<>();
+
+ @Override
+ public String toString() {
+ return "ClientState{" + Integer.toHexString(
+ System.identityHashCode(this)) + " mUid=" + mUid
+ + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+ }
+
+ ClientState(IInputMethodClientInvoker client,
+ IRemoteInputConnection fallbackInputConnection,
+ int uid, int pid, int selfReportedDisplayId,
+ IBinder.DeathRecipient clientDeathRecipient) {
+ mClient = client;
+ mFallbackInputConnection = fallbackInputConnection;
+ mUid = uid;
+ mPid = pid;
+ mSelfReportedDisplayId = selfReportedDisplayId;
+ mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+ mPid);
+ mClientDeathRecipient = clientDeathRecipient;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index f0e4b0f59b06..898d5a5e0644 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -19,6 +19,8 @@ package com.android.server.inputmethod;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -33,9 +35,26 @@ final class HardwareKeyboardShortcutController {
@GuardedBy("ImfLock.class")
private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
+ @UserIdInt
+ private final int mUserId;
+
+ @AnyThread
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
+ }
+
+ HardwareKeyboardShortcutController(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ mUserId = userId;
+ reset(methodMap);
+ }
+
@GuardedBy("ImfLock.class")
- void reset(@NonNull InputMethodUtils.InputMethodSettings settings) {
+ void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
mSubtypeHandles.clear();
+ final InputMethodUtils.InputMethodSettings settings =
+ new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
if (!imi.shouldShowInInputMethodPicker()) {
continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d722242b20b0..25ec6839efb5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,6 +48,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -127,7 +128,6 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
@@ -273,13 +273,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@NonNull
private final String[] mNonPreemptibleInputMethods;
+ // TODO(b/314150112): Move this to ClientController.
@UserIdInt
private int mLastSwitchUserId;
final Context mContext;
final Resources mRes;
private final Handler mHandler;
- private final InputMethodSettings mSettings;
+ @NonNull
+ private InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
new SparseBooleanArray(0);
@@ -324,8 +326,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// TODO: Instantiate mSwitchingController for each user.
@NonNull
private InputMethodSubtypeSwitchingController mSwitchingController;
- final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController();
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ @NonNull
+ private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
/**
* Tracks how many times {@link #mMethodMap} was updated.
@@ -391,7 +394,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* Record session state for an accessibility service.
*/
- private static class AccessibilitySessionState {
+ static class AccessibilitySessionState {
final ClientState mClient;
// Id of the accessibility service.
final int mId;
@@ -415,58 +418,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
- private final InputMethodManagerService mImms;
- private final IInputMethodClient mClient;
-
- ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client) {
- mImms = imms;
- mClient = client;
- }
-
- @Override
- public void binderDied() {
- mImms.removeClient(mClient);
- }
- }
-
- static final class ClientState {
- final IInputMethodClientInvoker mClient;
- final IRemoteInputConnection mFallbackInputConnection;
- final int mUid;
- final int mPid;
- final int mSelfReportedDisplayId;
- final InputBinding mBinding;
- final ClientDeathRecipient mClientDeathRecipient;
-
- boolean mSessionRequested;
- boolean mSessionRequestedForAccessibility;
- SessionState mCurSession;
- SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();
-
- @Override
- public String toString() {
- return "ClientState{" + Integer.toHexString(
- System.identityHashCode(this)) + " mUid=" + mUid
- + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
- }
-
- ClientState(IInputMethodClientInvoker client,
- IRemoteInputConnection fallbackInputConnection,
- int uid, int pid, int selfReportedDisplayId,
- ClientDeathRecipient clientDeathRecipient) {
- mClient = client;
- mFallbackInputConnection = fallbackInputConnection;
- mUid = uid;
- mPid = pid;
- mSelfReportedDisplayId = selfReportedDisplayId;
- mBinding = new InputBinding(null, mFallbackInputConnection.asBinder(), mUid, mPid);
- mClientDeathRecipient = clientDeathRecipient;
- }
- }
-
- @GuardedBy("ImfLock.class")
- final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+ /**
+ * Manages the IME clients.
+ */
+ private final ClientController mClientController;
/**
* Set once the system is ready to run third party code.
@@ -524,6 +479,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* The client that is currently bound to an input method.
*/
+ // TODO(b/314150112): Move this to ClientController.
@Nullable
private ClientState mCurClient;
@@ -864,8 +820,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Nullable
final String mImeSurfaceParentName;
- Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
- @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName, @SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
@Nullable String imeControlTargetName, @Nullable String imeTargetName,
@Nullable String imeSurfaceParentName) {
@@ -1629,7 +1586,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (userId != currentUserId) {
return;
}
- mSettings.switchCurrentUser(currentUserId);
+ mSettings = new InputMethodSettings(mMethodMap, userId);
if (mSystemReady) {
// We need to rebuild IMEs.
buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -1709,7 +1666,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap,
userId);
- mHardwareKeyboardShortcutController.reset(mSettings);
+ mHardwareKeyboardShortcutController =
+ new HardwareKeyboardShortcutController(mMethodMap, userId);
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1719,6 +1677,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+ mClientController = new ClientController(mPackageManagerInternal);
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1830,11 +1789,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
- // If the system is not ready or the device is not yed unlocked by the user, then we use
- // copy-on-write settings.
- final boolean useCopyOnWriteSettings =
- !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
- mSettings.switchCurrentUser(newUserId);
+ mSettings = new InputMethodSettings(mMethodMap, newUserId);
// Additional subtypes should be reset when the user is changed
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1876,7 +1831,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mLastSwitchUserId = newUserId;
if (mIsInteractive && clientToBeReset != null) {
- final ClientState cs = mClients.get(clientToBeReset.asBinder());
+ final ClientState cs =
+ mClientController.mClients.get(clientToBeReset.asBinder());
if (cs == null) {
// The client is already gone.
return;
@@ -1896,7 +1852,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!mSystemReady) {
mSystemReady = true;
final int currentUserId = mSettings.getCurrentUserId();
- mSettings.switchCurrentUser(currentUserId);
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -2214,43 +2169,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// actually running.
final int callerUid = Binder.getCallingUid();
final int callerPid = Binder.getCallingPid();
+
+ // TODO(b/314150112): Move the death recipient logic to ClientController when moving
+ // removeClient method.
+ final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
+ final IInputMethodClientInvoker clientInvoker =
+ IInputMethodClientInvoker.create(client, mHandler);
synchronized (ImfLock.class) {
- // TODO: Optimize this linear search.
- final int numClients = mClients.size();
- for (int i = 0; i < numClients; ++i) {
- final ClientState state = mClients.valueAt(i);
- if (state.mUid == callerUid && state.mPid == callerPid
- && state.mSelfReportedDisplayId == selfReportedDisplayId) {
- throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
- + "/displayId=" + selfReportedDisplayId + " is already registered.");
- }
- }
- final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
- try {
- client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- // We cannot fully avoid race conditions where the client UID already lost the access to
- // the given self-reported display ID, even if the client is not maliciously reporting
- // a fake display ID. Unconditionally returning SecurityException just because the
- // client doesn't pass display ID verification can cause many test failures hence not an
- // option right now. At the same time
- // context.getSystemService(InputMethodManager.class)
- // is expected to return a valid non-null instance at any time if we do not choose to
- // have the client crash. Thus we do not verify the display ID at all here. Instead we
- // later check the display ID every time the client needs to interact with the specified
- // display.
- final IInputMethodClientInvoker clientInvoker =
- IInputMethodClientInvoker.create(client, mHandler);
- mClients.put(client.asBinder(), new ClientState(clientInvoker, inputConnection,
- callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+ mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
+ deathRecipient, callerUid, callerPid);
}
}
+ // TODO(b/314150112): Move this to ClientController.
void removeClient(IInputMethodClient client) {
synchronized (ImfLock.class) {
- ClientState cs = mClients.remove(client.asBinder());
+ ClientState cs = mClientController.mClients.remove(client.asBinder());
if (cs != null) {
client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
clearClientSessionLocked(cs);
@@ -2280,6 +2214,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -2332,7 +2267,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
+ /**
+ * {@code true} when a {@link ClientState} has attached from starting the
+ * input connection.
+ */
@GuardedBy("ImfLock.class")
boolean hasAttachedClient() {
return mCurClient != null;
@@ -2976,10 +2914,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
void clearClientSessionsLocked() {
if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- clearClientSessionLocked(mClients.valueAt(i));
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
+ clearClientSessionLocked(mClientController.mClients.valueAt(i));
+ clearClientSessionForAccessibilityLocked(mClientController.mClients.valueAt(i));
}
finishSessionLocked(mEnabledSession);
@@ -3305,8 +3243,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mContext, mMethodMap, mSettings.getCurrentUserId());
}
-
- mHardwareKeyboardShortcutController.reset(mSettings);
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(mMethodMap);
+ } else {
+ mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+ mMethodMap, mSettings.getCurrentUserId());
+ }
sendOnNavButtonFlagsChangedLocked();
}
@@ -3509,9 +3452,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ " pref is disabled for user: " + userId);
return;
}
- if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
- Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
- throw new IllegalArgumentException("Delegator doesn't match Uid");
+ synchronized (ImfLock.class) {
+ if (!mClientController.verifyClientAndPackageMatch(client,
+ delegatorPackageName)) {
+ Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
+ throw new IllegalArgumentException("Delegator doesn't match Uid");
+ }
}
schedulePrepareStylusHandwritingDelegation(
userId, delegatePackageName, delegatorPackageName);
@@ -3537,30 +3483,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return true;
}
- private boolean verifyClientAndPackageMatch(
- @NonNull IInputMethodClient client, @NonNull String packageName) {
- ClientState cs;
- synchronized (ImfLock.class) {
- cs = mClients.get(client.asBinder());
- }
- if (cs == null) {
- throw new IllegalArgumentException("unknown client " + client.asBinder());
- }
- return InputMethodUtils.checkIfPackageBelongsToUid(
- mPackageManagerInternal, cs.mUid, packageName);
- }
-
private boolean verifyDelegator(
@NonNull IInputMethodClient client,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
@InputMethodManager.HandwritingDelegateFlags int flags) {
- if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
- Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
- + " startStylusHandwriting");
- return false;
- }
synchronized (ImfLock.class) {
+ if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) {
+ Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+ + " startStylusHandwriting");
+ return false;
+ }
boolean homeDelegatorAllowed =
(flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
!= 0;
@@ -3823,7 +3756,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return InputBindResult.INVALID_USER;
}
- final ClientState cs = mClients.get(client.asBinder());
+ final ClientState cs = mClientController.mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
@@ -3997,7 +3930,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// We need to check if this is the current client with
// focus in the window manager, to allow this call to
// be made before input is started in it.
- final ClientState cs = mClients.get(client.asBinder());
+ final ClientState cs =
+ mClientController.mClients.get(client.asBinder());
if (cs == null) {
ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
@@ -4621,7 +4555,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
ImeTracing.getInstance().startTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
@@ -4639,7 +4573,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
ImeTracing.getInstance().stopTrace(null /* printwriter */);
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
@@ -5328,7 +5262,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mContext, mMethodMap, mSettings.getCurrentUserId());
}
- mHardwareKeyboardShortcutController.reset(mSettings);
+ // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+ if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(mMethodMap);
+ } else {
+ mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+ mMethodMap, mSettings.getCurrentUserId());
+ }
sendOnNavButtonFlagsChangedLocked();
@@ -5878,10 +5818,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// We only have sessions when we bound to an input method. Remove this session
// from all clients.
if (getCurMethodLocked() != null) {
- final int numClients = mClients.size();
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
- accessibilityConnectionId);
+ clearClientSessionForAccessibilityLocked(
+ mClientController.mClients.valueAt(i), accessibilityConnectionId);
}
AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
accessibilityConnectionId);
@@ -6066,9 +6006,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
info.dump(p, " ");
}
p.println(" ClientStates:");
- final int numClients = mClients.size();
+ // TODO(b/314150112): move client related dump info to ClientController#dump
+ final int numClients = mClientController.mClients.size();
for (int i = 0; i < numClients; ++i) {
- final ClientState ci = mClients.valueAt(i);
+ final ClientState ci = mClientController.mClients.valueAt(i);
p.println(" " + ci + ":");
p.println(" client=" + ci.mClient);
p.println(" fallbackInputConnection=" + ci.mFallbackInputConnection);
@@ -6687,7 +6628,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
ArrayMap<IBinder, ClientState> clients;
synchronized (ImfLock.class) {
- clients = new ArrayMap<>(mClients);
+ clients = new ArrayMap<>(mClientController.mClients);
}
for (ClientState state : clients.values()) {
if (state != null) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index f9b06deb3142..fb57c09b9f73 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -215,7 +215,7 @@ final class InputMethodUtils {
private final ArrayMap<String, InputMethodInfo> mMethodMap;
@UserIdInt
- private int mCurrentUserId;
+ private final int mCurrentUserId;
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -229,18 +229,6 @@ final class InputMethodUtils {
InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
mMethodMap = methodMap;
- switchCurrentUser(userId);
- }
-
- /**
- * Must be called when the current user is changed.
- *
- * @param userId The user ID.
- */
- void switchCurrentUser(@UserIdInt int userId) {
- if (DEBUG) {
- Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
- }
mCurrentUserId = userId;
String ime = getSelectedInputMethod();
String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 07b333a0fda6..393e7efcce6e 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -198,7 +198,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
mIsConnected = true;
service = mService;
}
- service.onSessionActiveStateChanged(MediaSession2Record.this);
+
+ // TODO (b/318745416): Add support for FGS in MediaSession2. Passing a
+ // null playback state means the owning process will not be allowed to
+ // run in the foreground.
+ service.onSessionActiveStateChanged(MediaSession2Record.this,
+ /* playbackState= */ null);
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index cce66e28a8f9..53f780e4d19e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1144,7 +1144,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
mIsActive = active;
long token = Binder.clearCallingIdentity();
try {
- mService.onSessionActiveStateChanged(MediaSessionRecord.this);
+ mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2affdfcf5c7e..2cd3ab1ddbbb 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -260,7 +260,8 @@ public class MediaSessionService extends SystemService implements Monitor {
return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
}
- void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
+ void onSessionActiveStateChanged(
+ MediaSessionRecordImpl record, @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null) {
@@ -287,7 +288,9 @@ public class MediaSessionService extends SystemService implements Monitor {
user.mPriorityStack.onSessionActiveStateChanged(record);
}
setForegroundServiceAllowance(
- record, /* allowRunningInForeground= */ record.isActive());
+ record,
+ /* allowRunningInForeground= */ record.isActive()
+ && (playbackState == null || playbackState.isActive()));
mHandler.postSessionsChanged(record);
}
}
@@ -386,7 +389,9 @@ public class MediaSessionService extends SystemService implements Monitor {
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
if (playbackState != null) {
setForegroundServiceAllowance(
- record, playbackState.shouldAllowServiceToRunInForeground());
+ record,
+ /* allowRunningInForeground= */ playbackState.isActive()
+ && record.isActive());
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 6b7db2d8d071..a6f71c29b380 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -94,8 +94,6 @@ public final class NotificationAttentionHelper {
private static final float DEFAULT_VOLUME = 1.0f;
// TODO (b/291899544): remove for release
- private static final String POLITE_STRATEGY1 = "rule1";
- private static final String POLITE_STRATEGY2 = "rule2";
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
@@ -146,7 +144,6 @@ public final class NotificationAttentionHelper {
private boolean mNotificationCooldownApplyToAll;
private boolean mNotificationCooldownVibrateUnlocked;
- private boolean mEnablePoliteNotificationsFeature;
private final PolitenessStrategy mStrategy;
private int mCurrentWorkProfileId = UserHandle.USER_NULL;
@@ -192,9 +189,7 @@ public final class NotificationAttentionHelper {
.build();
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
- mEnablePoliteNotificationsFeature = Flags.politeNotifications();
-
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
mStrategy = getPolitenessStrategy();
} else {
mStrategy = null;
@@ -205,21 +200,23 @@ public final class NotificationAttentionHelper {
}
private PolitenessStrategy getPolitenessStrategy() {
- final String politenessStrategy = mFlagResolver.getStringValue(
- NotificationFlags.NOTIF_COOLDOWN_RULE);
+ if (Flags.crossAppPoliteNotifications()) {
+ PolitenessStrategy appStrategy = new StrategyPerApp(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
- if (POLITE_STRATEGY2.equals(politenessStrategy)) {
- return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ return new StrategyGlobal(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2));
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ appStrategy);
} else {
- if (!POLITE_STRATEGY1.equals(politenessStrategy)) {
- Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to "
- + POLITE_STRATEGY1);
- }
-
- return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+ return new StrategyPerApp(
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
@@ -266,7 +263,7 @@ public final class NotificationAttentionHelper {
mContext.getContentResolver().registerContentObserver(
SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
UserHandle.USER_ALL);
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
mContext.getContentResolver().registerContentObserver(
SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
UserHandle.USER_ALL);
@@ -280,7 +277,7 @@ public final class NotificationAttentionHelper {
}
private void loadUserSettings() {
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
try {
mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
@@ -301,11 +298,14 @@ public final class NotificationAttentionHelper {
mContext.getContentResolver(),
Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
UserHandle.USER_CURRENT) != 0;
- mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
- UserHandle.USER_CURRENT) != 0;
+ mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
+ if (Flags.vibrateWhileUnlocked()) {
+ mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+ DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+ UserHandle.USER_CURRENT) != 0;
+ }
} catch (Exception e) {
Log.e(TAG, "Failed to read Settings: " + e);
}
@@ -482,10 +482,10 @@ public final class NotificationAttentionHelper {
getPolitenessState(record));
}
record.setAudiblyAlerted(buzz || beep);
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
- record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis());
+ mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
}
}
return buzzBeepBlinkLoggingCode;
@@ -618,7 +618,7 @@ public final class NotificationAttentionHelper {
private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
// Check feature flag
- if (!mEnablePoliteNotificationsFeature) {
+ if (!Flags.politeNotifications()) {
return false;
}
@@ -1064,9 +1064,13 @@ public final class NotificationAttentionHelper {
// Volume for muted state
protected final float mVolumeMuted;
+ protected boolean mApplyPerPackage;
+ protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
int volumeMuted) {
mVolumeStates = new HashMap<>();
+ mLastUpdatedTimestampByPackage = new HashMap<>();
this.mTimeoutPolite = timeoutPolite;
this.mTimeoutMuted = timeoutMuted;
@@ -1076,10 +1080,38 @@ public final class NotificationAttentionHelper {
abstract void onNotificationPosted(NotificationRecord record);
+ /**
+ * Set true if the cooldown strategy should apply per app(package).
+ * Otherwise apply per conversation channel.
+ * @param applyPerPackage if the cooldown should be applied per app
+ */
+ void setApplyCooldownPerPackage(boolean applyPerPackage) {
+ mApplyPerPackage = applyPerPackage;
+ }
+
+ boolean shouldIgnoreNotification(final NotificationRecord record) {
+ // Ignore group summaries
+ return (record.getSbn().isGroup() && record.getSbn().getNotification()
+ .isGroupSummary());
+ }
+
+ /**
+ * Get the key that determines the grouping for the cooldown behavior.
+ *
+ * @param record the notification being posted
+ * @return the key to group this notification under
+ */
String getChannelKey(final NotificationRecord record) {
- // use conversationId if it's a conversation
+ // Use conversationId if it's a conversation
String channelId = record.getChannel().getConversationId() != null
? record.getChannel().getConversationId() : record.getChannel().getId();
+
+ // Use only the package name to apply cooldown per app, unless the user explicitly
+ // changed the channel notification sound => treat separately
+ if (mApplyPerPackage && !record.getChannel().hasUserSetSound()) {
+ channelId = "";
+ }
+
return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
+ ":" + channelId;
}
@@ -1121,12 +1153,59 @@ public final class NotificationAttentionHelper {
final String key = getChannelKey(record);
// reset to default state after user interaction
mVolumeStates.put(key, POLITE_STATE_DEFAULT);
- record.getChannel().setLastNotificationUpdateTimeMs(0);
+ setLastNotificationUpdateTimeMs(record, 0);
}
public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
}
+
+ void setLastNotificationUpdateTimeMs(final NotificationRecord record,
+ long timestampMillis) {
+ record.getChannel().setLastNotificationUpdateTimeMs(timestampMillis);
+ mLastUpdatedTimestampByPackage.put(record.getSbn().getPackageName(), timestampMillis);
+ }
+
+ long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+ if (record.getChannel().hasUserSetSound() || !mApplyPerPackage) {
+ return record.getChannel().getLastNotificationUpdateTimeMs();
+ } else {
+ return mLastUpdatedTimestampByPackage.getOrDefault(record.getSbn().getPackageName(),
+ 0L);
+ }
+ }
+
+ @PolitenessState int getNextState(@PolitenessState final int currState,
+ final long timeSinceLastNotif) {
+ @PolitenessState int nextState = currState;
+ switch (currState) {
+ case POLITE_STATE_DEFAULT:
+ if (timeSinceLastNotif < mTimeoutPolite) {
+ nextState = POLITE_STATE_POLITE;
+ }
+ break;
+ case POLITE_STATE_POLITE:
+ if (timeSinceLastNotif < mTimeoutMuted) {
+ nextState = POLITE_STATE_MUTED;
+ } else if (timeSinceLastNotif > mTimeoutPolite) {
+ nextState = POLITE_STATE_DEFAULT;
+ } else {
+ nextState = POLITE_STATE_POLITE;
+ }
+ break;
+ case POLITE_STATE_MUTED:
+ if (timeSinceLastNotif > mTimeoutMuted) {
+ nextState = POLITE_STATE_POLITE;
+ } else {
+ nextState = POLITE_STATE_MUTED;
+ }
+ break;
+ default:
+ Log.w(TAG, "getNextState unexpected volume state: " + currState);
+ break;
+ }
+ return nextState;
+ }
}
// TODO b/270456865: Only one of the two strategies will be released.
@@ -1143,72 +1222,51 @@ public final class NotificationAttentionHelper {
* after timeoutMuted.
* - Transitions back to the default state after a user interaction with a notification.
*/
- public static class Strategy1 extends PolitenessStrategy {
+ private static class StrategyPerApp extends PolitenessStrategy {
// Keep track of the number of notifications posted per channel
private final Map<String, Integer> mNumPosted;
// Reset to default state if number of posted notifications exceed this value when muted
private final int mMaxPostedForReset;
- public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted,
- int maxPosted) {
+ public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, int maxPosted) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
mNumPosted = new HashMap<>();
mMaxPostedForReset = maxPosted;
if (DEBUG) {
- Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyPerApp: " + timeoutPolite + " " + timeoutMuted);
}
}
@Override
public void onNotificationPosted(final NotificationRecord record) {
- long timeSinceLastNotif = System.currentTimeMillis()
- - record.getChannel().getLastNotificationUpdateTimeMs();
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
+
+ long timeSinceLastNotif =
+ System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
final String key = getChannelKey(record);
- @PolitenessState int volState = getPolitenessState(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ // Reset to default state if number of posted notifications exceed this value when muted
int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
mNumPosted.put(key, numPosted);
-
- switch (volState) {
- case POLITE_STATE_DEFAULT:
- if (timeSinceLastNotif < mTimeoutPolite) {
- volState = POLITE_STATE_POLITE;
- }
- break;
- case POLITE_STATE_POLITE:
- if (timeSinceLastNotif < mTimeoutMuted) {
- volState = POLITE_STATE_MUTED;
- } else if (timeSinceLastNotif > mTimeoutPolite) {
- volState = POLITE_STATE_DEFAULT;
- } else {
- volState = POLITE_STATE_POLITE;
- }
- break;
- case POLITE_STATE_MUTED:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_POLITE;
- } else {
- volState = POLITE_STATE_MUTED;
- }
- if (numPosted >= mMaxPostedForReset) {
- volState = POLITE_STATE_DEFAULT;
- mNumPosted.put(key, 0);
- }
- break;
- default:
- Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
- break;
+ if (currState == POLITE_STATE_MUTED && numPosted >= mMaxPostedForReset) {
+ nextState = POLITE_STATE_DEFAULT;
+ mNumPosted.put(key, 0);
}
if (DEBUG) {
Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
- + volState + " key: " + key + " numposted " + numPosted);
+ + nextState + " key: " + key + " numposted " + numPosted);
}
- mVolumeStates.put(key, volState);
+ mVolumeStates.put(key, nextState);
}
@Override
@@ -1219,61 +1277,98 @@ public final class NotificationAttentionHelper {
}
/**
- * Polite notification strategy 2:
- * - Transitions from default (loud) => muted state if a notification
- * alerts the same channel before timeoutPolite.
- * - Transitions from polite => default state if a notification
- * alerts the same channel before timeoutMuted.
- * - Transitions from muted => default state if a notification alerts after timeoutMuted,
- * otherwise transitions to the polite state.
- * - Transitions back to the default state after a user interaction with a notification.
+ * Global (cross-app) strategy.
*/
- public static class Strategy2 extends PolitenessStrategy {
- public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) {
+ private static class StrategyGlobal extends PolitenessStrategy {
+ private static final String COMMON_KEY = "cross_app_common_key";
+
+ private final PolitenessStrategy mAppStrategy;
+ private long mLastNotificationTimestamp = 0;
+
+ public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, PolitenessStrategy appStrategy) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ mAppStrategy = appStrategy;
+
if (DEBUG) {
- Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
}
}
@Override
- public void onNotificationPosted(final NotificationRecord record) {
- long timeSinceLastNotif = System.currentTimeMillis()
- - record.getChannel().getLastNotificationUpdateTimeMs();
+ void onNotificationPosted(NotificationRecord record) {
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
+
+ long timeSinceLastNotif =
+ System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
final String key = getChannelKey(record);
- @PolitenessState int volState = getPolitenessState(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
- switch (volState) {
- case POLITE_STATE_DEFAULT:
- if (timeSinceLastNotif < mTimeoutPolite) {
- volState = POLITE_STATE_MUTED;
- }
- break;
- case POLITE_STATE_POLITE:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_DEFAULT;
- }
- break;
- case POLITE_STATE_MUTED:
- if (timeSinceLastNotif > mTimeoutMuted) {
- volState = POLITE_STATE_DEFAULT;
- } else {
- volState = POLITE_STATE_POLITE;
- }
- break;
- default:
- Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
- break;
+ if (DEBUG) {
+ Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
+ + " vol state: " + nextState + " key: " + key);
}
- if (DEBUG) {
- Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
- + volState + " key: " + key);
+ mVolumeStates.put(key, nextState);
+
+ mAppStrategy.onNotificationPosted(record);
+ }
+
+ @Override
+ public float getSoundVolume(final NotificationRecord record) {
+ final @PolitenessState int globalVolState = getPolitenessState(record);
+ final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
+
+ // Prioritize the most polite outcome
+ if (globalVolState > appVolState) {
+ return super.getSoundVolume(record);
+ } else {
+ return mAppStrategy.getSoundVolume(record);
+ }
+ }
+
+ @Override
+ public void onUserInteraction(final NotificationRecord record) {
+ super.onUserInteraction(record);
+ mAppStrategy.onUserInteraction(record);
+ }
+
+ @Override
+ String getChannelKey(final NotificationRecord record) {
+ // If the user explicitly changed the channel notification sound:
+ // handle as a separate channel
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getChannelKey(record);
+ } else {
+ // Use one global key per user
+ return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
}
+ }
- mVolumeStates.put(key, volState);
+ @Override
+ public void setLastNotificationUpdateTimeMs(NotificationRecord record,
+ long timestampMillis) {
+ super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+ mLastNotificationTimestamp = timestampMillis;
+ }
+
+ long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+ if (record.getChannel().hasUserSetSound()) {
+ return super.getLastNotificationUpdateTimeMs(record);
+ } else {
+ return mLastNotificationTimestamp;
+ }
+ }
+
+ @Override
+ void setApplyCooldownPerPackage(boolean applyPerPackage) {
+ super.setApplyCooldownPerPackage(applyPerPackage);
+ mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
}
}
@@ -1338,7 +1433,7 @@ public final class NotificationAttentionHelper {
updateLightsLocked();
}
}
- if (mEnablePoliteNotificationsFeature) {
+ if (Flags.politeNotifications()) {
if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
mNotificationCooldownEnabled = Settings.System.getIntForUser(
mContext.getContentResolver(),
@@ -1363,13 +1458,16 @@ public final class NotificationAttentionHelper {
Settings.System.NOTIFICATION_COOLDOWN_ALL,
DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
!= 0;
+ mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
}
- if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
- mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+ if (Flags.vibrateWhileUnlocked()) {
+ if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
+ mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
mContext.getContentResolver(),
Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
UserHandle.USER_CURRENT) != 0;
+ }
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 6a46048e80e1..e3880c383632 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -118,6 +118,10 @@ public class NotificationHistoryManager {
}
}
+ public void onUserAdded(@UserIdInt int userId) {
+ mSettingsObserver.update(null, userId);
+ }
+
public void onUserStopped(@UserIdInt int userId) {
synchronized (mLock) {
mUserUnlockedStates.put(userId, false);
@@ -401,9 +405,7 @@ public class NotificationHistoryManager {
false, this, UserHandle.USER_ALL);
synchronized (mLock) {
for (UserInfo userInfo : mUserManager.getUsers()) {
- if (!userInfo.isProfile()) {
- update(null, userInfo.id);
- }
+ update(null, userInfo.id);
}
}
}
@@ -424,10 +426,7 @@ public class NotificationHistoryManager {
boolean historyEnabled = Settings.Secure.getIntForUser(resolver,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, userId)
!= 0;
- int[] profiles = mUserManager.getProfileIds(userId, true);
- for (int profileId : profiles) {
- onHistoryEnabledChanged(profileId, historyEnabled);
- }
+ onHistoryEnabledChanged(userId, historyEnabled);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7fbc0852c746..9ed3559c1389 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2025,6 +2025,8 @@ public class NotificationManagerService extends SystemService {
if (!mUserProfiles.isProfileUser(userId)) {
allowDefaultApprovedServices(userId);
}
+ mHistoryManager.onUserAdded(userId);
+ mSettingsObserver.update(null, userId);
}
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
@@ -2137,13 +2139,8 @@ public class NotificationManagerService extends SystemService {
mPreferencesHelper.updateBubblesEnabled();
}
if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
- final IntArray userIds = mUserProfiles.getCurrentProfileIds();
-
- for (int i = 0; i < userIds.size(); i++) {
- mArchive.updateHistoryEnabled(userIds.get(i),
- Settings.Secure.getIntForUser(resolver,
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
- userIds.get(i)) == 1);
+ for (UserInfo userInfo : mUm.getUsers()) {
+ update(uri, userInfo.id);
}
}
if (uri == null || NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI.equals(uri)) {
@@ -2164,6 +2161,17 @@ public class NotificationManagerService extends SystemService {
}
}
}
+
+ public void update(Uri uri, int userId) {
+ ContentResolver resolver = getContext().getContentResolver();
+ if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
+ mArchive.updateHistoryEnabled(userId,
+ Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+ userId) == 1);
+ // note: this setting is also handled in NotificationHistoryManager
+ }
+ }
}
private SettingsObserver mSettingsObserver;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 3244aff2a4dc..db64a750678b 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -893,48 +893,206 @@ public class ZenModeHelper {
}
void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
- @ConfigChangeOrigin int origin, boolean isNew) {
- // TODO: b/308671593,b/311406021 - Handle origins more precisely:
- // - USER can override anything and updates bitmask of user-modified fields;
- // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
- // - APP can only update if not user-modified.
- if (rule.enabled != automaticZenRule.isEnabled()) {
- rule.snoozing = false;
- }
- rule.name = automaticZenRule.getName();
- rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- rule.enabled = automaticZenRule.isEnabled();
- rule.modified = automaticZenRule.isModified();
- rule.zenPolicy = automaticZenRule.getZenPolicy();
+ @ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
- rule.zenDeviceEffects = fixZenDeviceEffects(
- rule.zenDeviceEffects,
- automaticZenRule.getDeviceEffects(),
- origin);
- }
- rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
-
- if (isNew) {
- rule.id = ZenModeConfig.newRuleId();
- rule.creationTime = System.currentTimeMillis();
- rule.component = automaticZenRule.getOwner();
- rule.pkg = pkg;
- }
+ // These values can always be edited by the app, so we apply changes immediately.
+ if (isNew) {
+ rule.id = ZenModeConfig.newRuleId();
+ rule.creationTime = System.currentTimeMillis();
+ rule.component = automaticZenRule.getOwner();
+ rule.pkg = pkg;
+ }
- if (Flags.modesApi()) {
+ rule.condition = null;
+ rule.conditionId = automaticZenRule.getConditionId();
+ if (rule.enabled != automaticZenRule.isEnabled()) {
+ rule.snoozing = false;
+ }
+ rule.enabled = automaticZenRule.isEnabled();
+ rule.configurationActivity = automaticZenRule.getConfigurationActivity();
rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
- rule.iconResName = drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
+ rule.iconResName =
+ drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
rule.triggerDescription = automaticZenRule.getTriggerDescription();
rule.type = automaticZenRule.getType();
+ // TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
+ rule.modified = automaticZenRule.isModified();
+
+ // Name is treated differently than other values:
+ // App is allowed to update name if the name was not modified by the user (even if
+ // other values have been modified). In this way, if the locale of an app changes,
+ // i18n of the rule name can still occur even if the user has customized the rule
+ // contents.
+ String previousName = rule.name;
+ if (isNew || doesOriginAlwaysUpdateValues(origin)
+ || (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
+ rule.name = automaticZenRule.getName();
+ }
+
+ // For the remaining values, rules can always have all values updated if:
+ // * the rule is newly added, or
+ // * the request comes from an origin that can always update values, like the user, or
+ // * the rule has not yet been user modified, and thus can be updated by the app.
+ boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin)
+ || rule.canBeUpdatedByApp();
+
+ // For all other values, if updates are not allowed, we discard the update.
+ if (!updateValues) {
+ return;
+ }
+
+ // Updates the bitmasks if the origin of the change is the user.
+ boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
+
+ if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
+ }
+ int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
+ automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ if (updateBitmask && rule.zenMode != newZenMode) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+
+ // Updates the values in the ZenRule itself.
+ rule.zenMode = newZenMode;
+
+ // Updates the bitmask and values for all policy fields, based on the origin.
+ rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(),
+ updateBitmask);
+ // Updates the bitmask and values for all device effect fields, based on the origin.
+ rule.zenDeviceEffects = updateZenDeviceEffects(
+ rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(),
+ origin == UPDATE_ORIGIN_APP, updateBitmask);
+ } else {
+ if (rule.enabled != automaticZenRule.isEnabled()) {
+ rule.snoozing = false;
+ }
+ rule.name = automaticZenRule.getName();
+ rule.condition = null;
+ rule.conditionId = automaticZenRule.getConditionId();
+ rule.enabled = automaticZenRule.isEnabled();
+ rule.modified = automaticZenRule.isModified();
+ rule.zenPolicy = automaticZenRule.getZenPolicy();
+ if (Flags.modesApi()) {
+ rule.zenDeviceEffects = updateZenDeviceEffects(
+ rule.zenDeviceEffects,
+ automaticZenRule.getDeviceEffects(),
+ origin == UPDATE_ORIGIN_APP,
+ origin == UPDATE_ORIGIN_USER);
+ }
+ rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+
+ if (isNew) {
+ rule.id = ZenModeConfig.newRuleId();
+ rule.creationTime = System.currentTimeMillis();
+ rule.component = automaticZenRule.getOwner();
+ rule.pkg = pkg;
+ }
}
}
/**
- * Fix {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
- *
+ * Returns true when fields can always be updated, based on the provided origin of an AZR
+ * change. (Note that regardless of origin, fields can always be updated if they're not already
+ * user modified.)
+ */
+ private static boolean doesOriginAlwaysUpdateValues(@ConfigChangeOrigin int origin) {
+ return origin == UPDATE_ORIGIN_USER || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ }
+
+ /**
+ * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule.
+ * Returns a policy based on {@code oldPolicy}, but with fields updated to match
+ * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to
+ * track these changes, if applicable based on {@code origin}.
+ */
+ @Nullable
+ private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy,
+ boolean updateBitmask) {
+ // If the update is to make the policy null, we don't need to update the bitmask,
+ // because it won't be stored anywhere anyway.
+ if (newPolicy == null) {
+ return null;
+ }
+
+ // If oldPolicy is null, we compare against the default policy when determining which
+ // fields in the bitmask should be marked as updated.
+ if (oldPolicy == null) {
+ oldPolicy = mDefaultConfig.toZenPolicy();
+ }
+
+ int userModifiedFields = oldPolicy.getUserModifiedFields();
+ if (updateBitmask) {
+ if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
+ userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
+ }
+ if (oldPolicy.getPriorityCallSenders() != newPolicy.getPriorityCallSenders()) {
+ userModifiedFields |= ZenPolicy.FIELD_CALLS;
+ }
+ if (oldPolicy.getPriorityConversationSenders()
+ != newPolicy.getPriorityConversationSenders()) {
+ userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
+ }
+ if (oldPolicy.getAllowedChannels() != newPolicy.getAllowedChannels()) {
+ userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
+ }
+ if (oldPolicy.getPriorityCategoryReminders()
+ != newPolicy.getPriorityCategoryReminders()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS;
+ }
+ if (oldPolicy.getPriorityCategoryEvents() != newPolicy.getPriorityCategoryEvents()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS;
+ }
+ if (oldPolicy.getPriorityCategoryRepeatCallers()
+ != newPolicy.getPriorityCategoryRepeatCallers()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS;
+ }
+ if (oldPolicy.getPriorityCategoryAlarms() != newPolicy.getPriorityCategoryAlarms()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_ALARMS;
+ }
+ if (oldPolicy.getPriorityCategoryMedia() != newPolicy.getPriorityCategoryMedia()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_MEDIA;
+ }
+ if (oldPolicy.getPriorityCategorySystem() != newPolicy.getPriorityCategorySystem()) {
+ userModifiedFields |= ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM;
+ }
+ // Visual effects
+ if (oldPolicy.getVisualEffectFullScreenIntent()
+ != newPolicy.getVisualEffectFullScreenIntent()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT;
+ }
+ if (oldPolicy.getVisualEffectLights() != newPolicy.getVisualEffectLights()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS;
+ }
+ if (oldPolicy.getVisualEffectPeek() != newPolicy.getVisualEffectPeek()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_PEEK;
+ }
+ if (oldPolicy.getVisualEffectStatusBar() != newPolicy.getVisualEffectStatusBar()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_STATUS_BAR;
+ }
+ if (oldPolicy.getVisualEffectBadge() != newPolicy.getVisualEffectBadge()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_BADGE;
+ }
+ if (oldPolicy.getVisualEffectAmbient() != newPolicy.getVisualEffectAmbient()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT;
+ }
+ if (oldPolicy.getVisualEffectNotificationList()
+ != newPolicy.getVisualEffectNotificationList()) {
+ userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST;
+ }
+ }
+
+ // After all bitmask changes have been made, sets the bitmask.
+ return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build();
+ }
+
+ /**
+ * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
+ * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to
+ * match {@code newEffects} where they differ, and updating the internal user-modified bitmask
+ * to track these changes, if applicable based on {@code origin}.
* <ul>
* <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
* intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
@@ -942,38 +1100,85 @@ public class ZenModeHelper {
* </ul>
*/
@Nullable
- private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
- @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) {
- // TODO: b/308671593,b/311406021 - Handle origins more precisely:
- // - USER can override anything and updates bitmask of user-modified fields;
- // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask;
- // - APP can only update if not user-modified.
- if (origin != UPDATE_ORIGIN_APP) {
- return newEffects;
- }
-
+ private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
+ @Nullable ZenDeviceEffects newEffects,
+ boolean isFromApp,
+ boolean updateBitmask) {
if (newEffects == null) {
return null;
}
- if (oldEffects != null) {
- return new ZenDeviceEffects.Builder(newEffects)
- .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
- .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
- .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
- .setShouldDisableTouch(oldEffects.shouldDisableTouch())
- .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
- .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
- .build();
- } else {
- return new ZenDeviceEffects.Builder(newEffects)
- .setShouldDisableAutoBrightness(false)
- .setShouldDisableTapToWake(false)
- .setShouldDisableTiltToWake(false)
- .setShouldDisableTouch(false)
- .setShouldMinimizeRadioUsage(false)
- .setShouldMaximizeDoze(false)
- .build();
+
+ // Since newEffects is not null, we want to adopt all the new provided device effects.
+ ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects);
+
+ if (isFromApp) {
+ if (oldEffects != null) {
+ // We can do this because we know we don't need to update the bitmask FROM_APP.
+ return builder
+ .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+ .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+ .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+ .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+ .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+ .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+ .build();
+ } else {
+ return builder
+ .setShouldDisableAutoBrightness(false)
+ .setShouldDisableTapToWake(false)
+ .setShouldDisableTiltToWake(false)
+ .setShouldDisableTouch(false)
+ .setShouldMinimizeRadioUsage(false)
+ .setShouldMaximizeDoze(false)
+ .build();
+ }
+ }
+
+ // If oldEffects is null, we compare against the default device effects object when
+ // determining which fields in the bitmask should be marked as updated.
+ if (oldEffects == null) {
+ oldEffects = new ZenDeviceEffects.Builder().build();
+ }
+
+ int userModifiedFields = oldEffects.getUserModifiedFields();
+ if (updateBitmask) {
+ if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE;
+ }
+ if (oldEffects.shouldSuppressAmbientDisplay()
+ != newEffects.shouldSuppressAmbientDisplay()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_SUPPRESS_AMBIENT_DISPLAY;
+ }
+ if (oldEffects.shouldDimWallpaper() != newEffects.shouldDimWallpaper()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_DIM_WALLPAPER;
+ }
+ if (oldEffects.shouldUseNightMode() != newEffects.shouldUseNightMode()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_NIGHT_MODE;
+ }
+ if (oldEffects.shouldDisableAutoBrightness()
+ != newEffects.shouldDisableAutoBrightness()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_DISABLE_AUTO_BRIGHTNESS;
+ }
+ if (oldEffects.shouldDisableTapToWake() != newEffects.shouldDisableTapToWake()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_DISABLE_TAP_TO_WAKE;
+ }
+ if (oldEffects.shouldDisableTiltToWake() != newEffects.shouldDisableTiltToWake()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_DISABLE_TILT_TO_WAKE;
+ }
+ if (oldEffects.shouldDisableTouch() != newEffects.shouldDisableTouch()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_DISABLE_TOUCH;
+ }
+ if (oldEffects.shouldMinimizeRadioUsage() != newEffects.shouldMinimizeRadioUsage()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_MINIMIZE_RADIO_USAGE;
+ }
+ if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
+ userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
+ }
}
+
+ // Since newEffects is not null, we want to adopt all the new provided device effects.
+ // Set the usermodifiedFields value separately, to reflect the updated bitmask.
+ return builder.setUserModifiedFields(userModifiedFields).build();
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -992,6 +1197,7 @@ public class ZenModeHelper {
.setOwner(rule.component)
.setConfigurationActivity(rule.configurationActivity)
.setTriggerDescription(rule.triggerDescription)
+ .setUserModifiedFields(rule.userModifiedFields)
.build();
} else {
azr = new AutomaticZenRule(rule.name, rule.component,
@@ -2023,6 +2229,7 @@ public class ZenModeHelper {
if (resId == 0) {
return null;
}
+ Objects.requireNonNull(packageName);
try {
final Resources res = mPm.getResourcesForApplication(packageName);
return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 49db7fc280d9..47967dbcaba2 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -56,4 +56,11 @@ flag {
namespace: "systemui"
description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
bug: "311051285"
+}
+
+flag {
+ name: "notification_test"
+ namespace: "systemui"
+ description: "Timing test, no functionality"
+ bug: "316931130"
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f6e7ef3d50e9..9ce3cb3abe4a 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,14 +41,13 @@ import android.system.Os;
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoParseException;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;
+import com.android.server.os.protobuf.CodedInputStream;
import libcore.io.IoUtils;
@@ -130,18 +129,21 @@ public final class NativeTombstoneManager {
return;
}
- String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
+ if (!isProtoFile) {
+ return;
+ }
- Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
+ Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
if (parsedTombstone.isPresent()) {
- processName = parsedTombstone.get().getProcessName();
+ BootReceiver.addTombstoneToDropBox(
+ mContext, path, parsedTombstone.get().getTombstone(),
+ parsedTombstone.get().getProcessName(), mTmpFileLock);
}
- BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
}
- private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
+ private Optional<ParsedTombstone> handleProtoTombstone(
+ File path, boolean addToList) {
final String filename = path.getName();
if (!filename.endsWith(".pb")) {
Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -171,7 +173,7 @@ public final class NativeTombstoneManager {
return Optional.empty();
}
- final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
+ final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
if (!parsedTombstone.isPresent()) {
IoUtils.closeQuietly(pfd);
return Optional.empty();
@@ -184,7 +186,7 @@ public final class NativeTombstoneManager {
previous.dispose();
}
- mTombstones.put(number, parsedTombstone.get());
+ mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
}
}
@@ -332,6 +334,27 @@ public final class NativeTombstoneManager {
}
}
+ static class ParsedTombstone {
+ TombstoneFile mTombstoneFile;
+ Tombstone mTombstone;
+ ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
+ mTombstoneFile = tombstoneFile;
+ mTombstone = tombstone;
+ }
+
+ public String getProcessName() {
+ return mTombstoneFile.getProcessName();
+ }
+
+ public TombstoneFile getTombstoneFile() {
+ return mTombstoneFile;
+ }
+
+ public Tombstone getTombstone() {
+ return mTombstone;
+ }
+ }
+
static class TombstoneFile {
final ParcelFileDescriptor mPfd;
@@ -414,67 +437,21 @@ public final class NativeTombstoneManager {
}
}
- static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
- final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
- final ProtoInputStream stream = new ProtoInputStream(is);
-
- int pid = 0;
- int uid = 0;
- String processName = null;
- String crashReason = "";
- String selinuxLabel = "";
-
- try {
- while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (stream.getFieldNumber()) {
- case (int) Tombstone.PID:
- pid = stream.readInt(Tombstone.PID);
- break;
-
- case (int) Tombstone.UID:
- uid = stream.readInt(Tombstone.UID);
- break;
-
- case (int) Tombstone.COMMAND_LINE:
- if (processName == null) {
- processName = stream.readString(Tombstone.COMMAND_LINE);
- }
- break;
+ static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
+ Tombstone tombstoneProto;
+ try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
+ final byte[] tombstoneBytes = is.readAllBytes();
- case (int) Tombstone.CAUSES:
- if (!crashReason.equals("")) {
- // Causes appear in decreasing order of likelihood. For now we only
- // want the most likely crash reason here, so ignore all others.
- break;
- }
- long token = stream.start(Tombstone.CAUSES);
- cause:
- while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (stream.getFieldNumber()) {
- case (int) Cause.HUMAN_READABLE:
- crashReason = stream.readString(Cause.HUMAN_READABLE);
- break cause;
-
- default:
- break;
- }
- }
- stream.end(token);
- break;
-
- case (int) Tombstone.SELINUX_LABEL:
- selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
- break;
-
- default:
- break;
- }
- }
- } catch (IOException | ProtoParseException ex) {
+ tombstoneProto = Tombstone.parseFrom(
+ CodedInputStream.newInstance(tombstoneBytes));
+ } catch (IOException ex) {
Slog.e(TAG, "Failed to parse tombstone", ex);
return Optional.empty();
}
+ int pid = tombstoneProto.getPid();
+ int uid = tombstoneProto.getUid();
+
if (!UserHandle.isApp(uid)) {
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
return Optional.empty();
@@ -491,6 +468,7 @@ public final class NativeTombstoneManager {
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
+ String selinuxLabel = tombstoneProto.getSelinuxLabel();
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
return Optional.empty();
@@ -502,11 +480,30 @@ public final class NativeTombstoneManager {
result.mAppId = appId;
result.mPid = pid;
result.mUid = uid;
- result.mProcessName = processName == null ? "" : processName;
+ result.mProcessName = getCmdLineProcessName(tombstoneProto);
result.mTimestampMs = timestampMs;
- result.mCrashReason = crashReason;
+ result.mCrashReason = getCrashReason(tombstoneProto);
- return Optional.of(result);
+ return Optional.of(new ParsedTombstone(result, tombstoneProto));
+ }
+
+ private static String getCmdLineProcessName(Tombstone tombstoneProto) {
+ for (String cmdline : tombstoneProto.getCommandLineList()) {
+ if (cmdline != null) {
+ return cmdline;
+ }
+ }
+ return "";
+ }
+
+ private static String getCrashReason(Tombstone tombstoneProto) {
+ for (Cause cause : tombstoneProto.getCausesList()) {
+ if (cause.getHumanReadable() != null
+ && !cause.getHumanReadable().equals("")) {
+ return cause.getHumanReadable();
+ }
+ }
+ return "";
}
public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 992d8eb8b1bb..dd9541e5dda1 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -175,6 +175,7 @@ import com.android.server.SystemConfig;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
@@ -957,6 +958,7 @@ final class InstallPackageHelper {
final Set<String> scannedPackages = new ArraySet<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
+ CriticalEventLog.getInstance().logInstallPackagesStarted();
boolean success = false;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 376b06105b8c..a4af5e71000c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -27,6 +27,7 @@ import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+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;
@@ -100,6 +101,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
@@ -210,7 +212,6 @@ public class PackageArchiver {
return;
}
- // TODO(b/278553670) Add special strings for the delete dialog
mPm.mInstallerService.uninstall(
new VersionedPackage(packageName,
PackageManager.VERSION_CODE_HIGHEST),
@@ -264,7 +265,7 @@ public class PackageArchiver {
try {
// TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
- getOrCreateUnarchiveIntentSender(userId, packageName),
+ getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
false /* showUnarchivalConfirmation= */);
} catch (Throwable t) {
@@ -329,7 +330,7 @@ public class PackageArchiver {
return true;
}
- private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) {
+ private IntentSender getOrCreateLauncherListener(int userId, String packageName) {
Pair<Integer, String> key = Pair.create(userId, packageName);
synchronized (mLauncherIntentSenders) {
IntentSender intentSender = mLauncherIntentSenders.get(key);
@@ -515,7 +516,6 @@ public class PackageArchiver {
/**
* Returns true if the app is archivable.
*/
- // TODO(b/299299569) Exclude system apps
public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -685,15 +685,14 @@ public class PackageArchiver {
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
- sessionParams.unarchiveIntentSender = statusReceiver;
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
- // TODO(b/316881759) Allow attaching multiple intentSenders to one session.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
userId);
if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) {
+ attachListenerToSession(statusReceiver, existingSessionId, userId);
return existingSessionId;
}
@@ -702,12 +701,34 @@ public class PackageArchiver {
installerPackage, mContext.getAttributionTag(),
installerUid,
userId);
+ attachListenerToSession(statusReceiver, sessionId, userId);
+
// TODO(b/297358628) Also cleanup sessions upon device restart.
mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId),
getUnarchiveForegroundTimeout());
return sessionId;
}
+ private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId,
+ int userId) {
+ PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId);
+ int status = session.getUnarchivalStatus();
+ // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK
+ // but hasn't created a session yet. Without this the listener would never receive a success
+ // response.
+ if (status == UNARCHIVAL_OK) {
+ notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(),
+ session.params.appPackageName, /* requiredStorageBytes= */ 0,
+ /* userActionIntent= */ null, Set.of(statusReceiver), userId);
+ return;
+ } else if (status != UNARCHIVAL_STATUS_UNSET) {
+ throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status"
+ + "%s but is still active.", session.sessionId, status));
+ }
+
+ session.registerUnarchivalListener(statusReceiver);
+ }
+
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
@@ -883,13 +904,7 @@ public class PackageArchiver {
void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
- @Nullable IntentSender unarchiveIntentSender, int userId) {
- if (unarchiveIntentSender == null) {
- // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick
- // succession.
- return;
- }
-
+ Set<IntentSender> unarchiveIntentSenders, int userId) {
final Intent broadcastIntent = new Intent();
broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
@@ -909,15 +924,16 @@ public class PackageArchiver {
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_DENIED);
- try {
- unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
- /* handler= */ null, /* requiredPermission= */ null,
- options.toBundle());
- } catch (IntentSender.SendIntentException e) {
- Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
- } finally {
- synchronized (mLauncherIntentSenders) {
- mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+ for (IntentSender intentSender : unarchiveIntentSenders) {
+ try {
+ intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ } finally {
+ synchronized (mLauncherIntentSenders) {
+ mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cbd65a4eb936..a9118d46b4ba 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -291,9 +291,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
@NonNull
private final RequestThrottle mSettingsWriteRequest = new RequestThrottle(IoThread.getHandler(),
() -> {
- synchronized (mSessions) {
- return writeSessionsLocked();
- }
+ return writeSessions();
});
public PackageInstallerService(Context context, PackageManagerService pm,
@@ -600,9 +598,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
- @GuardedBy("mSessions")
- private boolean writeSessionsLocked() {
- if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
+ private boolean writeSessions() {
+ if (LOGD) Slog.v(TAG, "writeSessions()");
+ final PackageInstallerSession[] sessions;
+ synchronized (mSessions) {
+ final int size = mSessions.size();
+ sessions = new PackageInstallerSession[size];
+ for (int i = 0; i < size; i++) {
+ sessions[i] = mSessions.valueAt(i);
+ }
+ }
FileOutputStream fos = null;
try {
@@ -611,9 +616,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
final TypedXmlSerializer out = Xml.resolveSerializer(fos);
out.startDocument(null, true);
out.startTag(null, TAG_SESSIONS);
- final int size = mSessions.size();
- for (int i = 0; i < size; i++) {
- final PackageInstallerSession session = mSessions.valueAt(i);
+ for (var session : sessions) {
session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
@@ -1756,26 +1759,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
binderUid, unarchiveId));
}
- IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
- if (unarchiveIntentSender == null) {
- throw new IllegalStateException(
- TextUtils.formatSimple(
- "Unarchival status for ID %s has already been set or a "
- + "session has been created for it already by the "
- + "caller.",
- unarchiveId));
- }
-
- // Execute expensive calls outside the sync block.
- mPm.mHandler.post(
- () -> mPackageArchiver.notifyUnarchivalListener(status,
- session.getInstallerPackageName(),
- session.params.appPackageName, requiredStorageBytes, userActionIntent,
- unarchiveIntentSender, userId));
- session.params.unarchiveIntentSender = null;
- if (status != UNARCHIVAL_OK) {
- Binder.withCleanCallingIdentity(session::abandon);
- }
+ session.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+ userActionIntent);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4adb60c34c52..117d03fd059b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -22,6 +22,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDAT
import static android.content.pm.DataLoaderType.INCREMENTAL;
import static android.content.pm.DataLoaderType.STREAMING;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
@@ -65,6 +67,7 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -97,6 +100,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
import android.content.pm.PackageInstaller.UserActionReason;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -771,6 +775,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final List<String> mResolvedInstructionSets = new ArrayList<>();
@GuardedBy("mLock")
private final List<String> mResolvedNativeLibPaths = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final Set<IntentSender> mUnarchivalListeners = new ArraySet<>();
+
@GuardedBy("mLock")
private File mInheritedFilesBase;
@GuardedBy("mLock")
@@ -796,6 +804,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION;
+ @UnarchivalStatus
+ private int mUnarchivalStatus = UNARCHIVAL_STATUS_UNSET;
+
private static final FileFilter sAddedApkFilter = new FileFilter() {
@Override
public boolean accept(File file) {
@@ -5088,6 +5099,44 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ void registerUnarchivalListener(IntentSender intentSender) {
+ synchronized (mLock) {
+ this.mUnarchivalListeners.add(intentSender);
+ }
+ }
+
+ Set<IntentSender> getUnarchivalListeners() {
+ synchronized (mLock) {
+ return new ArraySet<>(mUnarchivalListeners);
+ }
+ }
+
+ void reportUnarchivalStatus(@UnarchivalStatus int status, int unarchiveId,
+ long requiredStorageBytes, PendingIntent userActionIntent) {
+ if (getUnarchivalStatus() != UNARCHIVAL_STATUS_UNSET) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple(
+ "Unarchival status for ID %s has already been set or a session has "
+ + "been created for it already by the caller.",
+ unarchiveId));
+ }
+ mUnarchivalStatus = status;
+
+ // Execute expensive calls outside the sync block.
+ mPm.mHandler.post(
+ () -> mPm.mInstallerService.mPackageArchiver.notifyUnarchivalListener(status,
+ getInstallerPackageName(), params.appPackageName, requiredStorageBytes,
+ userActionIntent, getUnarchivalListeners(), userId));
+ if (status != UNARCHIVAL_OK) {
+ Binder.withCleanCallingIdentity(this::abandon);
+ }
+ }
+
+ @UnarchivalStatus
+ int getUnarchivalStatus() {
+ return this.mUnarchivalStatus;
+ }
+
/**
* Free up storage used by this session and its children.
* Must not be called on a child session.
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index d05e4c69427e..cf5de897cf5d 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -48,6 +48,7 @@ import java.util.function.BiFunction;
class PackageMonitorCallbackHelper {
private static final boolean DEBUG = false;
+ private static final String TAG = "PackageMonitorCallbackHelper";
@NonNull
private final Object mLock = new Object();
@@ -243,25 +244,33 @@ class PackageMonitorCallbackHelper {
return;
}
int registerUid = registerUser.getUid();
+ if (allowUids != null && registerUid != Process.SYSTEM_UID
+ && !ArrayUtils.contains(allowUids, registerUid)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
+ + ", uid " + registerUid);
+ }
+ return;
+ }
+ Intent newIntent = intent;
if (filterExtrasFunction != null) {
final Bundle extras = intent.getExtras();
if (extras != null) {
final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
- if (filteredExtras != null) {
- intent.replaceExtras(filteredExtras);
+ if (filteredExtras == null) {
+ // caller is unable to access this intent
+ if (DEBUG) {
+ Slog.w(TAG,
+ "Skip invoke PackageMonitorCallback for " + intent.getAction()
+ + " because null filteredExtras");
+ }
+ return;
}
+ newIntent = new Intent(newIntent);
+ newIntent.replaceExtras(filteredExtras);
}
}
- if (allowUids != null && registerUid != Process.SYSTEM_UID
- && !ArrayUtils.contains(allowUids, registerUid)) {
- if (DEBUG) {
- Slog.w("PackageMonitorCallbackHelper",
- "Skip invoke PackageMonitorCallback for " + intent.getAction()
- + ", uid " + registerUid);
- }
- return;
- }
- invokeCallback(callback, intent);
+ invokeCallback(callback, newIntent);
}));
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 752eb5315cc1..17c901e56407 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -254,6 +254,14 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
String packageName = verifications.valueAt(index).second;
AndroidPackage pkg = mConnection.getPackage(packageName);
+ if (pkg == null) {
+ if (DEBUG_BROADCASTS) {
+ Slog.d(TAG,
+ "Skip sendBroadcasts because null AndroidPackage for " + packageName);
+ }
+ continue;
+ }
+
String hostsString = buildHostsString(pkg);
Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp
new file mode 100644
index 000000000000..fa55bf0a30e5
--- /dev/null
+++ b/services/core/java/com/android/server/policy/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "policy_flags",
+ package: "com.android.server.policy",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "policy_flags_lib",
+ aconfig_declarations: "policy_flags",
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f651dbf591d1..bf669fba82ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1301,7 +1301,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0);
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
} else {
Slog.i(TAG, "Toggling theater mode on.");
@@ -1317,7 +1317,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
Slog.i(TAG, "Starting brightness boost.");
if (!interactive) {
- wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+ wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
}
mPowerManager.boostScreenBrightness(eventTime);
break;
@@ -5185,7 +5185,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5199,7 +5200,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(
+ whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5534,11 +5536,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void wakeUpFromWakeKey(KeyEvent event) {
- wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
+ wakeUpFromWakeKey(
+ event.getEventTime(),
+ event.getKeyCode(),
+ event.getAction() == KeyEvent.ACTION_DOWN);
}
- private void wakeUpFromWakeKey(long eventTime, int keyCode) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) {
+ private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index 392d0d4fdb52..a790950e7b9a 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -24,6 +24,9 @@ import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
import static android.view.KeyEvent.KEYCODE_POWER;
+import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.os.PowerManager;
@@ -31,7 +34,11 @@ import android.os.PowerManager.WakeReason;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
+import android.view.KeyEvent;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.server.LocalServices;
/** Policy controlling the decision and execution of window-related wake ups. */
class WindowWakeUpPolicy {
@@ -41,18 +48,27 @@ class WindowWakeUpPolicy {
private final Context mContext;
private final PowerManager mPowerManager;
+ private final Clock mClock;
private final boolean mAllowTheaterModeWakeFromKey;
private final boolean mAllowTheaterModeWakeFromPowerKey;
private final boolean mAllowTheaterModeWakeFromMotion;
- private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
private final boolean mAllowTheaterModeWakeFromCameraLens;
private final boolean mAllowTheaterModeWakeFromLidSwitch;
private final boolean mAllowTheaterModeWakeFromWakeGesture;
+ // The policy will handle input-based wake ups if this delegate is null.
+ @Nullable private WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
WindowWakeUpPolicy(Context context) {
+ this(context, Clock.SYSTEM_CLOCK);
+ }
+
+ @VisibleForTesting
+ WindowWakeUpPolicy(Context context, Clock clock) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mClock = clock;
final Resources res = context.getResources();
mAllowTheaterModeWakeFromKey = res.getBoolean(
@@ -62,14 +78,26 @@ class WindowWakeUpPolicy {
com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
mAllowTheaterModeWakeFromMotion = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
- mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean(
- com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
mAllowTheaterModeWakeFromCameraLens = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
mAllowTheaterModeWakeFromLidSwitch = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
mAllowTheaterModeWakeFromWakeGesture = res.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+ if (supportInputWakeupDelegate()) {
+ LocalServices.addService(WindowWakeUpPolicyInternal.class, new LocalService());
+ }
+ }
+
+ private final class LocalService implements WindowWakeUpPolicyInternal {
+ @Override
+ public void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate) {
+ if (!supportInputWakeupDelegate()) {
+ Slog.w(TAG, "Input wake up delegates not supported.");
+ return;
+ }
+ mInputWakeUpDelegate = delegate;
+ }
}
/**
@@ -77,31 +105,49 @@ class WindowWakeUpPolicy {
*
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+ * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode) {
+ boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
: mAllowTheaterModeWakeFromKey;
- return wakeUp(
+ if (!canWakeUp(wakeAllowedDuringTheaterMode)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from " + KeyEvent.keyCodeToString(keyCode));
+ return false;
+ }
+ if (mInputWakeUpDelegate != null
+ && mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ return true;
+ }
+ wakeUp(
eventTime,
- wakeAllowedDuringTheaterMode,
keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ return true;
}
/**
* Wakes up from a motion event.
*
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime) {
- return wakeUp(
- eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION");
+ boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
+ return false;
+ }
+ if (mInputWakeUpDelegate != null
+ && mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
+ return true;
+ }
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ return true;
}
/**
@@ -112,11 +158,12 @@ class WindowWakeUpPolicy {
* executed; {@code false} otherwise.
*/
boolean wakeUpFromCameraCover(long eventTime) {
- return wakeUp(
- eventTime,
- mAllowTheaterModeWakeFromCameraLens,
- WAKE_REASON_CAMERA_LAUNCH,
- "CAMERA_COVER");
+ if (!canWakeUp(mAllowTheaterModeWakeFromCameraLens)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from camera cover.");
+ return false;
+ }
+ wakeUp(eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
+ return true;
}
/**
@@ -126,11 +173,12 @@ class WindowWakeUpPolicy {
* executed; {@code false} otherwise.
*/
boolean wakeUpFromLid() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromLidSwitch,
- WAKE_REASON_LID,
- "LID");
+ if (!canWakeUp(mAllowTheaterModeWakeFromLidSwitch)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from lid.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
+ return true;
}
/**
@@ -140,11 +188,12 @@ class WindowWakeUpPolicy {
* executed; {@code false} otherwise.
*/
boolean wakeUpFromPowerKeyCameraGesture() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromPowerKey,
- WAKE_REASON_CAMERA_LAUNCH,
- "CAMERA_GESTURE_PREVENT_LOCK");
+ if (!canWakeUp(mAllowTheaterModeWakeFromPowerKey)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from power key camera gesture.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH, "CAMERA_GESTURE_PREVENT_LOCK");
+ return true;
}
/**
@@ -154,23 +203,23 @@ class WindowWakeUpPolicy {
* executed; {@code false} otherwise.
*/
boolean wakeUpFromWakeGesture() {
- return wakeUp(
- SystemClock.uptimeMillis(),
- mAllowTheaterModeWakeFromWakeGesture,
- WAKE_REASON_GESTURE,
- "GESTURE");
+ if (!canWakeUp(mAllowTheaterModeWakeFromWakeGesture)) {
+ if (DEBUG) Slog.d(TAG, "Unable to wake up from gesture.");
+ return false;
+ }
+ wakeUp(mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
+ return true;
}
- private boolean wakeUp(
- long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) {
+ private boolean canWakeUp(boolean wakeInTheaterMode) {
final boolean isTheaterModeEnabled =
Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
- if (!wakeInTheaterMode && isTheaterModeEnabled) {
- if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details);
- return false;
- }
+ return wakeInTheaterMode || !isTheaterModeEnabled;
+ }
+
+ /** Wakes up {@link PowerManager}. */
+ private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
- return true;
}
}
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
new file mode 100644
index 000000000000..66a003577e9a
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.LocalServices;
+
+/** Policy controlling the decision and execution of window-related wake ups. */
+@Keep
+public interface WindowWakeUpPolicyInternal {
+
+ /**
+ * A delegate that can choose to intercept Input-related wake ups.
+ *
+ * <p>This delegate is not meant to control policy decisions on whether or not to wake up. The
+ * policy makes that decision, and forwards the wake up request to the delegate as necessary.
+ * Therefore, the role of the delegate is to handle the actual "waking" of the device in
+ * response to the respective input event.
+ */
+ @Keep
+ interface InputWakeUpDelegate {
+ /**
+ * Wakes up the device in response to a key event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
+ * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
+ * @return {@code true} if the delegate handled the wake up. {@code false} if the delegate
+ * decided not to handle the wake up. The policy will execute the wake up in this case.
+ */
+ boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown);
+ /**
+ * Wakes up the device in response to a motion event.
+ *
+ * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+ * @param source the {@link android.view.InputDevice} source that caused the event.
+ * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
+ * @return {@code true} if the delegate handled the wake up. {@code false} if the delegate
+ * decided not to handle the wake up. The policy will execute the wake up in this case.
+ */
+ boolean wakeUpFromMotion(long eventTime, int source, boolean isDown);
+ }
+
+ /**
+ * Allows injecting a delegate for controlling input-based wake ups.
+ *
+ * <p>A delegate can be injected to the policy by system_server components only, and should be
+ * done via the {@link LocalServices} interface.
+ *
+ * <p>There can at most be one active delegate. If there's no delegate set (or if a {@code null}
+ * delegate is set), the policy will handle waking up the device in response to input events.
+ *
+ * @param delegate an implementation of {@link InputWakeUpDelegate} that handles input-based
+ * wake up requests. {@code null} to let the policy handle these wake ups.
+ */
+ @Keep
+ void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate);
+}
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
new file mode 100644
index 000000000000..ed981e0aca74
--- /dev/null
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.policy"
+
+flag {
+ name: "support_input_wakeup_delegate"
+ namespace: "wear_frameworks"
+ description: "Whether or not window policy allows injecting input wake-up delegate."
+ bug: "298055811"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING
index fa46acd9c39b..0de7c28c209b 100644
--- a/services/core/java/com/android/server/trust/TEST_MAPPING
+++ b/services/core/java/com/android/server/trust/TEST_MAPPING
@@ -12,6 +12,19 @@
]
}
],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.trust"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
"trust-tablet": [
{
"name": "TrustTests",
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9a85c42e1a10..5d716fc46f7a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -74,6 +74,7 @@ import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
@@ -1439,6 +1440,13 @@ public class TrustManagerService extends SystemService {
if (biometricManager == null) {
return new long[0];
}
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
+ && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
+ // Profiles with unified challenge have their own set of biometrics, but the device
+ // unlock happens via the parent user. In this case Keystore needs to be given the list
+ // of biometric SIDs from the parent user, not the profile.
+ userId = resolveProfileParent(userId);
+ }
return biometricManager.getAuthenticatorIds(userId);
}
@@ -1758,6 +1766,11 @@ public class TrustManagerService extends SystemService {
}
};
+ @VisibleForTesting
+ void waitForIdle() {
+ mHandler.runWithScissors(() -> {}, 0);
+ }
+
private boolean isTrustUsuallyManagedInternal(int userId) {
synchronized (mTrustUsuallyManagedForUser) {
int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index b0b66cfa247f..5c867017f4e0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -130,6 +130,28 @@ class WallpaperData {
*/
final Rect cropHint = new Rect(0, 0, 0, 0);
+ // Describes the context of a call to WallpaperManagerService#bindWallpaperComponentLocked
+ enum BindSource {
+ UNKNOWN,
+ CONNECT_LOCKED,
+ CONNECTION_TRY_TO_REBIND,
+ INITIALIZE_FALLBACK,
+ PACKAGE_UPDATE_FINISHED,
+ RESTORE_SETTINGS_LIVE_FAILURE,
+ RESTORE_SETTINGS_LIVE_SUCCESS,
+ RESTORE_SETTINGS_STATIC,
+ SET_LIVE,
+ SET_LIVE_TO_CLEAR,
+ SET_STATIC,
+ SWITCH_WALLPAPER_FAILURE,
+ SWITCH_WALLPAPER_SWITCH_USER,
+ SWITCH_WALLPAPER_UNLOCK_USER,
+ }
+
+ // Context in which this wallpaper was bound. Intended for use in resolving b/301073479 but may
+ // be useful after the issue is resolved as well.
+ BindSource mBindSource = BindSource.UNKNOWN;
+
// map of which -> File
private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
private final SparseArray<File> mCropFiles = new SparseArray<>();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 5f8bbe5f18ad..de98df55c3ea 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.JournaledFile;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.wallpaper.WallpaperData.BindSource;
import libcore.io.IoUtils;
@@ -314,6 +315,14 @@ class WallpaperDataParser {
wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
+ BindSource bindSource;
+ try {
+ bindSource = Enum.valueOf(BindSource.class,
+ getAttributeString(parser, "bindSource", BindSource.UNKNOWN.name()));
+ } catch (IllegalArgumentException | NullPointerException e) {
+ bindSource = BindSource.UNKNOWN;
+ }
+ wallpaper.mBindSource = bindSource;
int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0);
if (dimAmountsCount > 0) {
SparseArray<Float> allDimAmounts = new SparseArray<>(dimAmountsCount);
@@ -364,6 +373,11 @@ class WallpaperDataParser {
return parser.getAttributeFloat(null, name, defValue);
}
+ private String getAttributeString(XmlPullParser parser, String name, String defValue) {
+ String s = parser.getAttributeValue(null, name);
+ return (s != null) ? s : defValue;
+ }
+
void saveSettingsLocked(int userId, WallpaperData wallpaper, WallpaperData lockWallpaper) {
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
@@ -423,6 +437,7 @@ class WallpaperDataParser {
}
out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
+ out.attribute(null, "bindSource", wallpaper.mBindSource.name());
int dimAmountsCount = wallpaper.mUidToDimAmount.size();
out.attributeInt(null, "dimAmountsCount", dimAmountsCount);
if (dimAmountsCount > 0) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 1485b961789c..3782b429f93a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -122,6 +122,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wallpaper.WallpaperData.BindSource;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -335,6 +336,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
};
// If this was the system wallpaper, rebind...
+ wallpaper.mBindSource = BindSource.SET_STATIC;
bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
callback);
}
@@ -354,6 +356,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
};
+ wallpaper.mBindSource = BindSource.SET_STATIC;
bindWallpaperComponentLocked(mImageWallpaper, true /* force */,
false /* fromUser */, wallpaper, callback);
} else if (isAppliedToLock) {
@@ -811,6 +814,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "Failed attaching wallpaper on display", e);
if (wallpaper != null && !wallpaper.wallpaperUpdating
&& connection.getConnectedEngineSize() == 0) {
+ wallpaper.mBindSource = BindSource.CONNECT_LOCKED;
bindWallpaperComponentLocked(null /* componentName */, false /* force */,
false /* fromUser */, wallpaper, null /* reply */);
}
@@ -1035,6 +1039,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final ComponentName wpService = mWallpaper.wallpaperComponent;
// The broadcast of package update could be delayed after service disconnected. Try
// to re-bind the service for 10 seconds.
+ mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND;
if (bindWallpaperComponentLocked(
wpService, true, false, mWallpaper, null)) {
mWallpaper.connection.scheduleTimeoutLocked();
@@ -1321,6 +1326,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
wallpaper.wallpaperUpdating = false;
clearWallpaperComponentLocked(wallpaper);
+ wallpaper.mBindSource = BindSource.PACKAGE_UPDATE_FINISHED;
if (!bindWallpaperComponentLocked(wpService, false, false,
wallpaper, null)) {
Slog.w(TAG, "Wallpaper " + wpService
@@ -1711,6 +1717,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mHomeWallpaperWaitingForUnlock) {
final WallpaperData systemWallpaper =
getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+ systemWallpaper.mBindSource = BindSource.SWITCH_WALLPAPER_UNLOCK_USER;
switchWallpaper(systemWallpaper, null);
// TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
notifyCallbacksLocked(systemWallpaper);
@@ -1718,6 +1725,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLockWallpaperWaitingForUnlock) {
final WallpaperData lockWallpaper =
getWallpaperSafeLocked(userId, FLAG_LOCK);
+ lockWallpaper.mBindSource = BindSource.SWITCH_WALLPAPER_UNLOCK_USER;
switchWallpaper(lockWallpaper, null);
notifyCallbacksLocked(lockWallpaper);
}
@@ -1838,6 +1846,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// delete them in order to show the default wallpaper.
clearWallpaperBitmaps(wallpaper);
+ fallback.mBindSource = BindSource.SWITCH_WALLPAPER_FAILURE;
bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = true;
if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = true;
@@ -2963,6 +2972,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
*/
boolean forceRebind = force || (same && systemIsBoth && which == FLAG_SYSTEM);
+ newWallpaper.mBindSource =
+ (name == null) ? BindSource.SET_LIVE_TO_CLEAR : BindSource.SET_LIVE;
bindSuccess = bindWallpaperComponentLocked(name, /* force */
forceRebind, /* fromUser */ true, newWallpaper, reply);
if (bindSuccess) {
@@ -3530,6 +3541,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mFallbackWallpaper = new WallpaperData(systemUserId, FLAG_SYSTEM);
mFallbackWallpaper.allowBackup = false;
mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked();
+ mFallbackWallpaper.mBindSource = BindSource.INITIALIZE_FALLBACK;
bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false,
mFallbackWallpaper, null);
}
@@ -3553,11 +3565,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper.allowBackup = true; // by definition if it was restored
if (wallpaper.nextWallpaperComponent != null
&& !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) {
+ wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
wallpaper, null)) {
// No such live wallpaper or other failure; fall back to the default
// live wallpaper (since the profile being restored indicated that the
// user had selected a live rather than static one).
+ wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_FAILURE;
bindWallpaperComponentLocked(null, false, false, wallpaper, null);
}
success = true;
@@ -3575,6 +3589,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
+ " id=" + wallpaper.wallpaperId);
if (success) {
mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata
+ wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC;
bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, true, false,
wallpaper, null);
}
@@ -3608,7 +3623,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.print(wallpaper.wallpaperId);
pw.print(": mWhich="); pw.print(wallpaper.mWhich);
- pw.print(": mSystemWasBoth="); pw.println(wallpaper.mSystemWasBoth);
+ pw.print(": mSystemWasBoth="); pw.print(wallpaper.mSystemWasBoth);
+ pw.print(": mBindSource="); pw.println(wallpaper.mBindSource.name());
pw.println(" Display state:");
mWallpaperDisplayHelper.forEachDisplayData(wpSize -> {
pw.print(" displayId=");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index febcc052064e..3a792d079db2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2181,7 +2181,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The result receiver is the transition receiver, which will handle the shared element
// exit transition.
mHasSceneTransition = options.getAnimationType() == ANIM_SCENE_TRANSITION
- && options.getResultReceiver() != null;
+ && options.getSceneTransitionInfo() != null
+ && options.getSceneTransitionInfo().getResultReceiver() != null;
final PendingIntent usageReport = options.getUsageTimeReport();
if (usageReport != null) {
appTimeTracker = new AppTimeTracker(usageReport);
@@ -2494,7 +2495,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData");
mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams);
- if (task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
+ if ((!mStyleFillsParent && task.getChildCount() > 1)
+ || task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
+ // Case 1:
+ // If it is moving a Task{[0]=main activity, [1]=translucent activity} to front, use
+ // shared starting window so that the transition doesn't need to wait for the activity
+ // behind the translucent activity. Also, onFirstWindowDrawn will check all visible
+ // activities are drawn in the task to remove the snapshot starting window.
+ // Case 2:
// Associate with the task so if this activity is resized by task fragment later, the
// starting window can keep the same bounds as the task.
associateStartingDataWithTask();
@@ -5178,16 +5186,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return mPendingOptions;
}
- ActivityOptions takeOptions() {
- if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
- + Debug.getCallers(6));
+ ActivityOptions.SceneTransitionInfo takeSceneTransitionInfo() {
+ if (DEBUG_TRANSITION) {
+ Slog.i(TAG, "Taking SceneTransitionInfo for " + this + " callers="
+ + Debug.getCallers(6));
+ }
if (mPendingOptions == null) return null;
final ActivityOptions opts = mPendingOptions;
mPendingOptions = null;
- // Strip sensitive information from options before sending it to app.
- opts.setRemoteTransition(null);
- opts.setRemoteAnimationAdapter(null);
- return opts;
+ return opts.getSceneTransitionInfo();
}
RemoteTransition takeRemoteTransition() {
@@ -6153,7 +6160,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StartActivityItem.obtain(token, takeOptions()));
+ StartActivityItem.obtain(token, takeSceneTransitionInfo()));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
}
@@ -6258,8 +6265,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void handleAlreadyVisible() {
try {
- if (returningOptions != null) {
- app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle());
+ if (returningOptions != null
+ && returningOptions.getAnimationType() == ANIM_SCENE_TRANSITION
+ && returningOptions.getSceneTransitionInfo() != null) {
+ app.getThread().scheduleOnNewSceneTransitionInfo(token,
+ returningOptions.getSceneTransitionInfo());
}
} catch(RemoteException e) {
}
@@ -6608,10 +6618,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return hasProcess() && !app.isCrashing() && !app.isNotResponding();
}
- void startFreezingScreenLocked(int configChanges) {
- startFreezingScreenLocked(app, configChanges);
- }
-
void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
if (mayFreezeScreenLocked(app)) {
if (getParent() == null) {
@@ -8095,12 +8101,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* Set the last reported configuration to the client. Should be called whenever
* a new merged configuration is sent to the client for this activity.
*/
- void setLastReportedConfiguration(@NonNull MergedConfiguration config) {
- setLastReportedConfiguration(config.getGlobalConfiguration(),
- config.getOverrideConfiguration());
- }
-
- private void setLastReportedConfiguration(Configuration global, Configuration override) {
+ void setLastReportedConfiguration(@NonNull Configuration global,
+ @NonNull Configuration override) {
mLastReportedConfiguration.setConfiguration(global, override);
}
@@ -9634,7 +9636,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
configChangeFlags |= changes;
if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
&& !mTransitionController.isShellTransitionsEnabled()) {
- startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+ startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
}
final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
!= getWindowConfiguration().getDisplayRotation()
@@ -10621,6 +10623,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+ if (task != null && task.mSharedStartingData != null) {
+ final WindowState startingWin = task.topStartingWindow();
+ if (startingWin != null && startingWin.mSyncState == SYNC_STATE_READY
+ && mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
+ // The sync is ready if a drawn starting window covered the task.
+ return true;
+ }
+ }
if (!super.isSyncFinished(group)) return false;
if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
.isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1872f6e1fdb8..ec0e3e7f178f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,7 +134,6 @@ import android.os.UserManager;
import android.os.WorkSource;
import android.provider.MediaStore;
import android.util.ArrayMap;
-import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -820,8 +819,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
proc.pauseConfigurationDispatch();
try {
- r.startFreezingScreenLocked(proc, 0);
-
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
r.lastLaunchTime = SystemClock.uptimeMillis();
@@ -908,13 +905,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
mService.getAppWarningsLocked().onStartActivity(r);
- // Because we could be starting an Activity in the system process this may not go
- // across a Binder interface which would create a new Configuration. Consequently
- // we have to always create a new Configuration here.
final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
- final MergedConfiguration mergedConfiguration = new MergedConfiguration(
- procConfig, r.getMergedOverrideConfiguration());
- r.setLastReportedConfiguration(mergedConfiguration);
+ final Configuration overrideConfig = r.getMergedOverrideConfiguration();
+ r.setLastReportedConfiguration(procConfig, overrideConfig);
logIfTransactionTooLarge(r.intent, r.getSavedState());
@@ -932,13 +925,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
r.intent, System.identityHashCode(r), r.info,
- // TODO: Have this take the merged configuration instead of separate global
- // and override configs.
- mergedConfiguration.getGlobalConfiguration(),
- mergedConfiguration.getOverrideConfiguration(), deviceId,
+ procConfig, overrideConfig, deviceId,
r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
- results, newIntents, r.takeOptions(), isTransitionForward,
+ results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 975fdc0ade5d..0f9e5b0b2892 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -179,6 +179,12 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
if (physicalDisplayUpdated) {
onDisplayUpdated(transition, fromRotation, startBounds);
} else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ mDisplayContent.mTransitionController.setDisplaySyncMethod(displayChange,
+ mDisplayContent);
transition.setAllReady();
}
});
@@ -204,6 +210,22 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
return new DisplayInfo(mNonOverrideDisplayInfo);
}
+ @NonNull
+ private TransitionRequestInfo.DisplayChange getCurrentDisplayChange(int fromRotation,
+ @NonNull Rect startBounds) {
+ final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
+ mDisplayContent.mInitialDisplayHeight);
+ final int toRotation = mDisplayContent.getRotation();
+
+ final TransitionRequestInfo.DisplayChange displayChange =
+ new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
+ displayChange.setStartAbsBounds(startBounds);
+ displayChange.setEndAbsBounds(endBounds);
+ displayChange.setStartRotation(fromRotation);
+ displayChange.setEndRotation(toRotation);
+ return displayChange;
+ }
+
/**
* Called when physical display is updated, this could happen e.g. on foldable
* devices when the physical underlying display is replaced. This method should be called
@@ -214,16 +236,10 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
*/
private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
@NonNull Rect startBounds) {
- final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
- mDisplayContent.mInitialDisplayHeight);
final int toRotation = mDisplayContent.getRotation();
final TransitionRequestInfo.DisplayChange displayChange =
- new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId());
- displayChange.setStartAbsBounds(startBounds);
- displayChange.setEndAbsBounds(endBounds);
- displayChange.setStartRotation(fromRotation);
- displayChange.setEndRotation(toRotation);
+ getCurrentDisplayChange(fromRotation, startBounds);
displayChange.setPhysicalDisplayChanged(true);
mDisplayContent.mTransitionController.requestStartTransition(transition,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 460a68f48ff6..63ca5929e34d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -40,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -959,15 +960,17 @@ public class DisplayPolicy {
case TYPE_BASE_APPLICATION:
- // A non-translucent main app window isn't allowed to fit insets, as it would create
- // a hole on the display!
+ // A non-translucent main app window isn't allowed to fit insets or display cutouts,
+ // as it would create a hole on the display!
if (attrs.isFullscreen() && win.mActivityRecord != null
&& win.mActivityRecord.fillsParent()
- && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
- && attrs.getFitInsetsTypes() != 0) {
+ && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
+ && (attrs.getFitInsetsTypes() != 0
+ || (attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && attrs.layoutInDisplayCutoutMode
+ != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)) {
throw new IllegalArgumentException("Illegal attributes: Main activity window"
- + " that isn't translucent trying to fit insets: "
- + attrs.getFitInsetsTypes()
+ + " that isn't translucent trying to fit insets or display cutouts."
+ " attrs=" + attrs);
}
break;
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
new file mode 100644
index 000000000000..3862b82512c3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
+ * not threadsafe and any call site should hold {@link WindowManagerGlobalLock}
+ */
+public class SensitiveContentPackages {
+ private final ArraySet<PackageInfo> mProtectedPackages = new ArraySet<>();
+
+ /** Returns {@code true} if package/uid pair should be blocked from screen capture */
+ public boolean shouldBlockScreenCaptureForApp(String pkg, int uid) {
+ for (int i = 0; i < mProtectedPackages.size(); i++) {
+ PackageInfo info = mProtectedPackages.valueAt(i);
+ if (info != null && info.mPkg.equals(pkg) && info.mUid == uid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
+ public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
+ mProtectedPackages.clear();
+ mProtectedPackages.addAll(packageInfos);
+ }
+
+ void dump(PrintWriter pw) {
+ final String innerPrefix = " ";
+ pw.println("SensitiveContentPackages:");
+ pw.println(innerPrefix + "Packages that should block screen capture ("
+ + mProtectedPackages.size() + "):");
+ for (PackageInfo info : mProtectedPackages) {
+ pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid);
+ }
+ }
+
+ /** Helper class that represents a package/uid pair */
+ public static class PackageInfo {
+ private String mPkg;
+ private int mUid;
+
+ public PackageInfo(String pkg, int uid) {
+ this.mPkg = pkg;
+ this.mUid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PackageInfo)) return false;
+ PackageInfo that = (PackageInfo) o;
+ return mUid == that.mUid && Objects.equals(mPkg, that.mPkg);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPkg, mUid);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d556f095ae50..a7a6bf2ed2a1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1411,12 +1411,13 @@ class Task extends TaskFragment {
return isUidPresent;
}
+ WindowState topStartingWindow() {
+ return getWindow(w -> w.mAttrs.type == TYPE_APPLICATION_STARTING);
+ }
+
ActivityRecord topActivityContainsStartingWindow() {
- if (getParent() == null) {
- return null;
- }
- return getActivity((r) -> r.getWindow(window ->
- window.getBaseType() == TYPE_APPLICATION_STARTING) != null);
+ final WindowState startingWindow = topStartingWindow();
+ return startingWindow != null ? startingWindow.mActivityRecord : null;
}
/**
@@ -3698,6 +3699,16 @@ class Task extends TaskFragment {
}
wc.assignLayer(t, layer++);
+ // Boost the adjacent TaskFragment for dimmer if needed.
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment != null && taskFragment.isEmbedded()
+ && taskFragment.isVisibleRequested()) {
+ final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
+ adjacentTf.assignLayer(t, layer++);
+ }
+ }
+
// Place the decor surface just above the owner TaskFragment.
if (mDecorSurfaceContainer != null && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
@@ -4784,6 +4795,7 @@ class Task extends TaskFragment {
}
if (top.isAttached()) {
top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ top.mWaitForEnteringPinnedMode = false;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f51bd1be158c..f56759f9481c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -39,6 +39,7 @@ import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -2995,6 +2996,30 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}, false /* traverseTopToBottom */);
}
+ boolean shouldBoostDimmer() {
+ if (asTask() != null || !isDimmingOnParentTask()) {
+ // early return if not embedded or should not dim on parent Task.
+ return false;
+ }
+
+ final TaskFragment adjacentTf = getAdjacentTaskFragment();
+ if (adjacentTf == null) {
+ // early return if no adjacent TF.
+ return false;
+ }
+
+ if (getParent().mChildren.indexOf(adjacentTf) < getParent().mChildren.indexOf(this)) {
+ // early return if this TF already has higher z-ordering.
+ return false;
+ }
+
+ // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
+ return forAllWindows(
+ (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
+ && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
+ || w.mActivityRecord.isVisible()), true);
+ }
+
@Override
Dimmer getDimmer() {
// If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c3aca6fe84d1..708d63e27ec2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -634,8 +634,8 @@ class TransitionController {
}
/** Sets the sync method for the display change. */
- private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
- @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+ void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+ @NonNull DisplayContent displayContent) {
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
if (startBounds == null || endBounds == null) return;
@@ -686,7 +686,7 @@ class TransitionController {
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
if (newTransition != null && displayChange != null && trigger != null
&& trigger.asDisplayContent() != null) {
- setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
+ setDisplaySyncMethod(displayChange, trigger.asDisplayContent());
}
}
if (trigger != null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 33ef3c5629e3..a9f0554b2bec 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -183,18 +183,21 @@ class WallpaperController {
&& (mWallpaperTarget == w || w.isDrawFinishedLw())) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
+ mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
if (w == mWallpaperTarget && w.isAnimating(TRANSITION | PARENTS)) {
// The current wallpaper target is animating, so we'll look behind it for
// another possible target and figure out what is going on later.
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Win " + w + ": token animating, looking behind.");
}
- mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
// While the keyguard is going away, both notification shade and a normal activity such
// as a launcher can satisfy criteria for a wallpaper target. In this case, we should
// chose the normal activity, otherwise wallpaper becomes invisible when a new animation
// starts before the keyguard going away animation finishes.
- return w.mActivityRecord != null;
+ if (w.mActivityRecord == null && mDisplayContent.isKeyguardGoingAway()) {
+ return false;
+ }
+ return true;
}
return false;
};
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e28262dfbe2f..bdea1bc40f3a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3938,7 +3938,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Nullable
BLASTSyncEngine.SyncGroup getSyncGroup() {
if (mSyncGroup != null) return mSyncGroup;
- if (mParent != null) return mParent.getSyncGroup();
+ WindowContainer<?> parent = mParent;
+ while (parent != null) {
+ if (parent.mSyncGroup != null) {
+ return parent.mSyncGroup;
+ }
+ parent = parent.mParent;
+ }
return null;
}
@@ -3972,7 +3978,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @param cancel If true, this is being finished because it is leaving the sync group rather
* than due to the sync group completing.
*/
- void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+ void finishSync(Transaction outMergedTransaction, @Nullable BLASTSyncEngine.SyncGroup group,
boolean cancel) {
if (mSyncState == SYNC_STATE_NONE) return;
final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
@@ -4000,7 +4006,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (!isVisibleRequested()) {
return true;
}
- if (mSyncState == SYNC_STATE_NONE) {
+ if (mSyncState == SYNC_STATE_NONE && getSyncGroup() != null) {
+ Slog.i(TAG, "prepareSync in isSyncFinished: " + this);
prepareSync();
}
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
@@ -4059,16 +4066,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
if (newParent == null) {
// This is getting removed.
+ final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
if (oldParent.mSyncState != SYNC_STATE_NONE) {
// In order to keep the transaction in sync, merge it into the parent.
- finishSync(oldParent.mSyncTransaction, getSyncGroup(), true /* cancel */);
- } else if (mSyncGroup != null) {
- // This is watched directly by the sync-group, so merge this transaction into
- // into the sync-group so it isn't lost
- finishSync(mSyncGroup.getOrphanTransaction(), mSyncGroup, true /* cancel */);
+ finishSync(oldParent.mSyncTransaction, syncGroup, true /* cancel */);
+ } else if (syncGroup != null) {
+ // This is watched by the sync-group, so merge this transaction into the
+ // sync-group for not losing the operations in the transaction.
+ finishSync(syncGroup.getOrphanTransaction(), syncGroup, true /* cancel */);
} else {
- throw new IllegalStateException("This container is in sync mode without a sync"
- + " group: " + this);
+ Slog.wtf(TAG, this + " is in sync mode without a sync group");
+ // Make sure the removal transaction take effect.
+ finishSync(getPendingTransaction(), null /* group */, true /* cancel */);
}
return;
} else if (mSyncGroup == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 516d37c0136a..22b690e85c35 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -51,6 +51,7 @@ import android.window.ScreenCapture;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import java.lang.annotation.Retention;
import java.util.List;
@@ -1012,4 +1013,12 @@ public abstract class WindowManagerInternal {
*/
public abstract void setOrientationRequestPolicy(boolean respected,
int[] fromOrientations, int[] toOrientations);
+
+ /**
+ * Set whether screen capture should be disabled for all windows of a specific app windows based
+ * on sensitive content protections.
+ *
+ * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
+ */
+ public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 502912a98816..c63cc4373472 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -123,6 +123,7 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
+import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -312,6 +313,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContextInfo;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -366,6 +368,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -1053,6 +1056,9 @@ public class WindowManagerService extends IWindowManager.Stub
SystemPerformanceHinter mSystemPerformanceHinter;
+ @GuardedBy("mGlobalLock")
+ final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages();
+
/** Listener to notify activity manager about app transitions. */
final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
= new WindowManagerInternal.AppTransitionListener() {
@@ -1797,7 +1803,12 @@ public class WindowManagerService extends IWindowManager.Stub
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
- win.getParent().assignChildLayers();
+ if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
+ // Assign child layers from the parent Task if the Activity is embedded.
+ win.getTask().assignChildLayers();
+ } else {
+ win.getParent().assignChildLayers();
+ }
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
@@ -1931,12 +1942,13 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Set whether screen capture is disabled for all windows of a specific user from
- * the device policy cache.
+ * the device policy cache, or specific windows based on sensitive content protections.
*/
@Override
public void refreshScreenCaptureDisabled() {
int callingUid = Binder.getCallingUid();
- if (callingUid != SYSTEM_UID) {
+ // MY_UID (Process.myUid()) should always be SYSTEM_UID here, but using MY_UID for tests
+ if (callingUid != MY_UID) {
throw new SecurityException("Only system can call refreshScreenCaptureDisabled.");
}
@@ -7169,6 +7181,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
mSystemPerformanceHinter.dump(pw, "");
mTrustedPresentationListenerController.dump(pw);
+ mSensitiveContentPackages.dump(pw);
}
}
@@ -8550,6 +8563,14 @@ public class WindowManagerService extends IWindowManager.Stub
InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
return inputTarget == null ? null : inputTarget.getWindowToken();
}
+
+ @Override
+ public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+ synchronized (mGlobalLock) {
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0b43be700b0d..56f2bc3d3e3b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1896,6 +1896,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
+
+ if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+ if (mWmService.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) {
+ return true;
+ }
+ }
+
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index dfa9dcecfbb5..775570cb08ba 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,6 +37,7 @@ cc_library_static {
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
+ "com_android_server_BootReceiver.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -94,6 +95,16 @@ cc_library_static {
header_libs: [
"bionic_libc_platform_headers",
],
+
+ static_libs: [
+ "libunwindstack",
+ ],
+
+ whole_static_libs: [
+ "libdebuggerd_tombstone_proto_to_text",
+ ],
+
+ runtime_libs: ["libdexfile"],
}
cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index cc08488742b2..15eb7c64a40c 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,3 +33,7 @@ per-file com_android_server_companion_virtual_InputController.cpp = file:/servic
# Bug component : 158088 = per-file *AnrTimer*
per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
+
+# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
+per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
new file mode 100644
index 000000000000..3892d284dafb
--- /dev/null
+++ b/services/core/jni/com_android_server_BootReceiver.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include <libdebuggerd/tombstone.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <sstream>
+
+#include "jni.h"
+#include "tombstone.pb.h"
+
+namespace android {
+
+static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
+ ss << line << std::endl;
+}
+
+static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
+ jbyteArray tombstoneBytes) {
+ Tombstone tombstone;
+ tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
+ env->GetArrayLength(tombstoneBytes));
+
+ std::stringstream tombstoneString;
+
+ tombstone_proto_to_text(tombstone,
+ std::bind(&writeToString, std::ref(tombstoneString),
+ std::placeholders::_1, std::placeholders::_2));
+
+ return env->NewStringUTF(tombstoneString.str().c_str());
+}
+
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"getTombstoneText", "([B)Ljava/lang/String;",
+ (jstring*)com_android_server_BootReceiver_getTombstoneText},
+};
+
+int register_com_android_server_BootReceiver(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
+ NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 97b18fac91f4..1e48aced0041 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -486,9 +486,11 @@ class AnrTimerService::Ticker {
void remove(AnrTimerService const* service) {
AutoMutex _l(lock_);
timer_id_t front = headTimerId();
- for (auto i = running_.begin(); i != running_.end(); i++) {
+ for (auto i = running_.begin(); i != running_.end(); ) {
if (i->service == service) {
- running_.erase(i);
+ i = running_.erase(i);
+ } else {
+ i++;
}
}
if (front != headTimerId()) restartLocked();
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f3158d11b9a4..a4b1f841d3bc 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,10 +52,10 @@ int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
-int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
@@ -66,6 +66,7 @@ int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
+int register_com_android_server_BootReceiver(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -114,10 +115,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
- register_android_server_utils_AnrTimer(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
+ register_android_server_utils_AnrTimer(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
@@ -128,6 +129,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
+ register_com_android_server_BootReceiver(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
register_com_android_server_SystemClockTime(env);
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index 4e954653bcbb..73c13cec6bc2 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -49,7 +49,7 @@
<xs:complexType name="display">
<xs:sequence>
- <xs:element name="address" type="xs:nonNegativeInteger"/>
+ <xs:group ref="displayReference" minOccurs="1" maxOccurs="1"/>
<xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="powerThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
@@ -67,4 +67,11 @@
</xs:simpleType>
</xs:attribute>
</xs:complexType>
+
+ <xs:group name="displayReference">
+ <xs:choice>
+ <xs:element name="address" type="xs:nonNegativeInteger" minOccurs="0"/>
+ <xs:element name="port" type="xs:nonNegativeInteger" minOccurs="0"/>
+ </xs:choice>
+ </xs:group>
</xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 195cae5aee14..0d2f6a89ac25 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -3,22 +3,24 @@ package com.android.server.display.config.layout {
public class Display {
ctor public Display();
- method public java.math.BigInteger getAddress();
+ method public java.math.BigInteger getAddress_optional();
method public String getBrightnessThrottlingMapId();
method public String getDisplayGroup();
method public java.math.BigInteger getLeadDisplayAddress();
+ method public java.math.BigInteger getPort_optional();
method public String getPosition();
method public String getPowerThrottlingMapId();
method public String getRefreshRateThermalThrottlingMapId();
method public String getRefreshRateZoneId();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
- method public void setAddress(java.math.BigInteger);
+ method public void setAddress_optional(java.math.BigInteger);
method public void setBrightnessThrottlingMapId(String);
method public void setDefaultDisplay(boolean);
method public void setDisplayGroup(String);
method public void setEnabled(boolean);
method public void setLeadDisplayAddress(java.math.BigInteger);
+ method public void setPort_optional(java.math.BigInteger);
method public void setPosition(String);
method public void setPowerThrottlingMapId(String);
method public void setRefreshRateThermalThrottlingMapId(String);
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 6e82907d4361..fd21a326b640 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,8 @@ import static java.util.stream.Collectors.toMap;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
import android.os.ISystemConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -108,6 +110,15 @@ public class SystemConfigService extends SystemService {
"Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
}
+
+ @Override
+ public List<String> getPreventUserDisablePackages() {
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ return SystemConfig.getInstance().getPreventUserDisablePackages().stream()
+ .filter(preventUserDisablePackage ->
+ pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
+ .collect(toList());
+ }
};
public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1185a4e4f93b..86ad49458c48 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2965,6 +2965,12 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
+ if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+ t.traceBegin("StartSensitiveContentProtectionManager");
+ mSystemServiceManager.startService(SensitiveContentProtectionManagerService.class);
+ t.traceEnd();
+ }
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 52eae21f9e66..a70802ad3337 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -57,9 +57,13 @@ android_robolectric_test {
],
static_libs: [
"androidx.test.ext.truth",
+ "Settings-robo-testutils",
+ "SettingsLib-robo-testutils",
],
instrumentation_for: "FrameworksServicesLib",
+
+ upstream: true,
}
filegroup {
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 8b9efb312efe..569786b1e26f 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -57,6 +57,8 @@ android_robolectric_test {
// Include the testing libraries
libs: [
"mockito-robolectric-prebuilt",
+ "Settings-robo-testutils",
+ "SettingsLib-robo-testutils",
"platform-test-annotations",
"testng",
"truth",
@@ -64,4 +66,6 @@ android_robolectric_test {
instrumentation_for: "BackupFrameworksServicesLib",
+ upstream: true,
+
}
diff --git a/services/robotests/backup/config/robolectric.properties b/services/robotests/backup/config/robolectric.properties
index 850557a9b693..1ebf6d423fe2 100644
--- a/services/robotests/backup/config/robolectric.properties
+++ b/services/robotests/backup/config/robolectric.properties
@@ -1 +1,3 @@
-sdk=NEWEST_SDK \ No newline at end of file
+sdk=NEWEST_SDK
+looperMode=LEGACY
+shadows=com.android.server.testing.shadows.FrameworkShadowLooper
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index ee5a534fe005..6839a06a0d9a 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -57,6 +57,7 @@ import java.nio.file.attribute.FileTime;
ShadowBackupDataOutput.class,
ShadowEnvironment.class,
ShadowFullBackup.class,
+ ShadowSigningInfo.class,
})
public class AppMetadataBackupWriterTest {
private static final String TEST_PACKAGE = "com.test.package";
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
new file mode 100644
index 000000000000..53d807c2d3c9
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.fullbackup;
+
+import static android.os.Build.VERSION_CODES.P;
+
+import android.content.pm.SigningInfo;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(value = SigningInfo.class, minSdk = P)
+public class ShadowSigningInfo {
+}
diff --git a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
index 4949091646dd..009276359209 100644
--- a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
@@ -35,6 +35,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
import java.util.concurrent.CountDownLatch;
@@ -45,6 +46,7 @@ import java.util.concurrent.TimeUnit;
*/
@RunWith(RobolectricTestRunner.class)
@Presubmit
+@LooperMode(LooperMode.Mode.LEGACY)
public class NtpNetworkTimeHelperTest {
private static final long MOCK_NTP_TIME = 1519930775453L;
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
index 16d16cda42ed..3681bd4c6588 100644
--- a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
@@ -21,12 +21,15 @@ import android.os.Looper;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.LooperShadowPicker;
+import org.robolectric.shadows.ShadowLegacyLooper;
import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPausedLooper;
import java.util.Optional;
-@Implements(value = Looper.class)
-public class FrameworkShadowLooper extends ShadowLooper {
+@Implements(value = Looper.class, shadowPicker = FrameworkShadowLooper.Picker.class)
+public class FrameworkShadowLooper extends ShadowLegacyLooper {
@RealObject private Looper mLooper;
private Optional<Boolean> mIsCurrentThread = Optional.empty();
@@ -45,4 +48,10 @@ public class FrameworkShadowLooper extends ShadowLooper {
}
return Thread.currentThread() == mLooper.getThread();
}
+
+ public static class Picker extends LooperShadowPicker<ShadowLooper> {
+ public Picker() {
+ super(FrameworkShadowLooper.class, ShadowPausedLooper.class);
+ }
+ }
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index 4a9948668bc4..1da67597643f 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -95,7 +95,6 @@ public class ShadowApplicationPackageManager
sPackageAppEnabledStates.put(packageName, Integer.valueOf(newState)); // flags unused here.
}
- @Override
protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
if (!sPackageInfos.containsKey(packageName)) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index ffe6dc5d1c63..56423b961813 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -40,6 +40,7 @@ android_test {
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "ravenwood-junit",
"services.core",
"service-permission.stubs.system_server",
"servicestests-core-utils",
@@ -66,6 +67,28 @@ android_test {
},
}
+android_ravenwood_test {
+ name: "FrameworksInputMethodSystemServerTests_host",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ "framework",
+ "mockito_ravenwood",
+ "ravenwood-runtime",
+ "ravenwood-utils",
+ "services",
+ ],
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+ srcs: [
+ "src/com/android/server/inputmethod/**/ClientControllerTest.java",
+ ],
+ sdk_version: "test_current",
+ auto_gen_config: true,
+}
+
android_test {
name: "FrameworksImeTests",
defaults: [
@@ -88,6 +111,7 @@ android_test {
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "ravenwood-junit",
"services.core",
"service-permission.stubs.system_server",
"servicestests-core-utils",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
new file mode 100644
index 000000000000..3c8f5c9578d3
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.view.Display;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class ClientControllerTest {
+ private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
+ private static final int ANY_CALLER_UID = 1;
+ private static final int ANY_CALLER_PID = 1;
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true).build();
+
+ @Mock
+ private PackageManagerInternal mMockPackageManagerInternal;
+
+ @Mock(extraInterfaces = IBinder.class)
+ private IInputMethodClient mClient;
+
+ @Mock
+ private IRemoteInputConnection mConnection;
+
+ @Mock
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ private Handler mHandler;
+
+ private ClientController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ mController = new ClientController(mMockPackageManagerInternal);
+ when(mClient.asBinder()).thenReturn((IBinder) mClient);
+ }
+
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+ public void testAddClient_cannotAddTheSameClientTwice() {
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+
+ synchronized (ImfLock.class) {
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
+ ANY_CALLER_UID, ANY_CALLER_PID);
+
+ SecurityException thrown = assertThrows(SecurityException.class,
+ () -> {
+ synchronized (ImfLock.class) {
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+ mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+ }
+ });
+ assertThat(thrown.getMessage()).isEqualTo(
+ "uid=1/pid=1/displayId=0 is already registered");
+ }
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 3199e062418f..438bea458c47 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VI
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
+import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -68,8 +69,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
super.setUp();
mVisibilityApplier =
(DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
- mInputMethodManagerService.setAttachedClientForTesting(
- mock(InputMethodManagerService.ClientState.class));
+ mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 8cc3408ad79b..567792ecb6e0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -25,6 +25,7 @@ import android.view.DisplayAddress;
import androidx.test.filters.SmallTest;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
@@ -45,6 +46,7 @@ public class DeviceStateToLayoutMapTest {
private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
@Mock DisplayIdProducer mDisplayIdProducerMock;
+ @Mock DisplayManagerFlags mMockFlags;
@Before
public void setUp() throws IOException {
@@ -52,7 +54,7 @@ public class DeviceStateToLayoutMapTest {
Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
- setupDeviceStateToLayoutMap();
+ setupDeviceStateToLayoutMap(getContent());
}
//////////////////
@@ -268,6 +270,41 @@ public class DeviceStateToLayoutMapTest {
IllegalArgumentException.class, () -> layout.postProcessLocked());
}
+ @Test
+ public void testPortInLayout_disabledFlag() {
+ Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(false);
+ assertThrows("Expected IllegalArgumentException when using <port>",
+ IllegalArgumentException.class,
+ () -> setupDeviceStateToLayoutMap(getPortContent()));
+ }
+
+ @Test
+ public void testPortInLayout_readLayout() throws Exception {
+ Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(true);
+ setupDeviceStateToLayoutMap(getPortContent());
+
+ Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(DisplayAddress.fromPortAndModel(123, null),
+ /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+ /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
+ testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(78910L),
+ /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ null,
+ mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+ /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+ /* refreshRateZoneId= */ null,
+ /* refreshRateThermalThrottlingMapId= */ null,
+ /* powerThrottlingMapId= */ null);
+ testLayout.postProcessLocked();
+
+ assertEquals(testLayout, configLayout);
+ }
+
////////////////////
// Helper Methods //
////////////////////
@@ -287,13 +324,28 @@ public class DeviceStateToLayoutMapTest {
/* powerThrottlingMapId= */ null);
}
- private void setupDeviceStateToLayoutMap() throws IOException {
+ private void setupDeviceStateToLayoutMap(String content) throws IOException {
Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
- mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+ Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock, mMockFlags,
tempFile.toFile());
}
+ private String getPortContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<layouts>\n"
+ + "<layout>\n"
+ + "<state>0</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<port>123</port>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+ + "</layouts>\n";
+ }
+
private String getContent() {
return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ "<layouts>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index eb6e8b4469f0..ad4d91ff8ba0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -96,6 +96,8 @@ public class DisplayBrightnessStateTest {
.append(displayBrightnessState.isSlowChange())
.append("\n maxBrightness:")
.append(displayBrightnessState.getMaxBrightness())
+ .append("\n minBrightness:")
+ .append(displayBrightnessState.getMinBrightness())
.append("\n customAnimationRate:")
.append(displayBrightnessState.getCustomAnimationRate())
.append("\n shouldUpdateScreenBrightnessSetting:")
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index e370f5501865..c67e7c5ae61e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -41,6 +41,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
import android.os.Temperature;
import android.provider.Settings;
import android.util.SparseArray;
@@ -109,6 +110,43 @@ public final class DisplayDeviceConfigTest {
}
@Test
+ public void testDefaultValues() {
+ when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType))
+ .thenReturn("test_light_sensor");
+ when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
+
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false,
+ mFlags);
+
+ assertEquals(DisplayDeviceConfig.BRIGHTNESS_DEFAULT,
+ mDisplayDeviceConfig.getBrightnessDefault(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampFastDecrease(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampFastIncrease(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampSlowDecreaseIdle(), ZERO_DELTA);
+ assertEquals(PowerManager.BRIGHTNESS_MAX,
+ mDisplayDeviceConfig.getBrightnessRampSlowIncreaseIdle(), ZERO_DELTA);
+ assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis());
+ assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis());
+ assertEquals(0, mDisplayDeviceConfig.getBrightnessRampDecreaseMaxIdleMillis());
+ assertEquals(0, mDisplayDeviceConfig.getBrightnessRampIncreaseMaxIdleMillis());
+ assertNull(mDisplayDeviceConfig.getNits());
+ assertNull(mDisplayDeviceConfig.getBacklight());
+ assertEquals(0.3f, mDisplayDeviceConfig.getBacklightFromBrightness(0.3f), ZERO_DELTA);
+ assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
+ assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+ assertNull(mDisplayDeviceConfig.getProximitySensor().type);
+ assertNull(mDisplayDeviceConfig.getProximitySensor().name);
+ assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ }
+
+ @Test
public void testConfigValuesFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
@@ -681,6 +719,7 @@ public final class DisplayDeviceConfigTest {
assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
+ assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
assertEquals(brightnessIntToFloat(35),
mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
@@ -807,6 +846,24 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
}
+ @Test
+ public void testIsAutoBrightnessAvailable_EnabledInConfigResource() throws IOException {
+ when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
+
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ }
+
+ @Test
+ public void testIsAutoBrightnessAvailable_DisabledInConfigResource() throws IOException {
+ when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(false);
+
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertFalse(mDisplayDeviceConfig.isAutoBrightnessAvailable());
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1176,7 +1233,7 @@ public final class DisplayDeviceConfigTest {
+ "<nits>" + NITS[2] + "</nits>\n"
+ "</point>\n"
+ "</screenBrightnessMap>\n"
- + "<autoBrightness>\n"
+ + "<autoBrightness enabled=\"true\">\n"
+ "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+ (includeIdleMode ? getRampSpeedsIdle() : "")
@@ -1593,6 +1650,7 @@ public final class DisplayDeviceConfigTest {
when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType))
.thenReturn("test_light_sensor");
+ when(mResources.getBoolean(R.bool.config_automatic_brightness_available)).thenReturn(true);
when(mResources.getInteger(
R.integer.config_autoBrightnessBrighteningLightDebounce))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
index dafbbb3e0140..33d30200faaa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
@@ -16,16 +16,24 @@
package com.android.server.display
+import android.content.Context
+import android.os.Looper
import android.view.Display
import androidx.test.filters.SmallTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
import java.util.concurrent.Executor
@SmallTest
@@ -39,11 +47,16 @@ class DisplayPowerStateTest {
private val mockBlanker = mock<DisplayBlanker>()
private val mockColorFade = mock<ColorFade>()
private val mockExecutor = mock<Executor>()
+ private val mockContext = mock<Context>()
@Before
fun setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare()
+ }
displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON,
mockExecutor)
+ whenever(mockColorFade.prepare(eq(mockContext), anyInt())).thenReturn(true)
}
@Test
@@ -56,4 +69,31 @@ class DisplayPowerStateTest {
verify(mockColorFade).destroy()
}
+
+ @Test
+ fun `GIVEN not prepared WHEN draw runnable is called THEN colorFade not drawn`() {
+ displayPowerState.mColorFadeDrawRunnable.run()
+
+ verify(mockColorFade, never()).draw(anyFloat())
+ }
+ @Test
+ fun `GIVEN prepared WHEN draw runnable is called THEN colorFade is drawn`() {
+ displayPowerState.prepareColorFade(mockContext, ColorFade.MODE_FADE)
+ clearInvocations(mockColorFade)
+
+ displayPowerState.mColorFadeDrawRunnable.run()
+
+ verify(mockColorFade).draw(anyFloat())
+ }
+
+ @Test
+ fun `GIVEN prepared AND stopped WHEN draw runnable is called THEN colorFade is not drawn`() {
+ displayPowerState.prepareColorFade(mockContext, ColorFade.MODE_FADE)
+ clearInvocations(mockColorFade)
+ displayPowerState.stop()
+
+ displayPowerState.mColorFadeDrawRunnable.run()
+
+ verify(mockColorFade, never()).draw(anyFloat())
+ }
} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 00f98924eff3..c92ce254cae4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -176,6 +176,10 @@ public class LocalDisplayAdapterTest {
when(mockArray.length()).thenReturn(0);
when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
.thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_displayCutoutSideOverrideArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.getStringArray(R.array.config_mainBuiltInDisplayCutoutSideOverride))
+ .thenReturn(new String[]{});
when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 28ec89629df0..bed6f928a55c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -49,8 +49,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -83,7 +84,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import java.io.File;
import java.io.InputStream;
@@ -111,14 +111,14 @@ public class LogicalDisplayMapperTest {
private final DisplayIdProducer mIdProducer = (isDefault) ->
isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+ private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock FoldSettingProvider mFoldSettingProviderMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
- new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
@Mock DisplayManagerFlags mFlagsMock;
@Mock DisplayAdapter mDisplayAdapterMock;
@@ -131,6 +131,8 @@ public class LogicalDisplayMapperTest {
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mDeviceStateToLayoutMapSpy =
+ spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE));
mDisplayDeviceRepo = new DisplayDeviceRepository(
new DisplayManagerService.SyncRoot(),
new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index e58b3e891b70..990c3830b76c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -58,9 +58,14 @@ public final class BrightnessReasonTest {
@Test
public void setModifierDoesntSetIfModifierIsBeyondExtremes() {
- int extremeModifier = 0x16;
+ int extremeModifier = 0x40; // equal to BrightnessReason.MODIFIER_MASK * 2
+
+ // reset modifier
+ mBrightnessReason.setModifier(0);
+
+ // test extreme
mBrightnessReason.setModifier(extremeModifier);
- assertEquals(mBrightnessReason.getModifier(), BrightnessReason.MODIFIER_LOW_POWER);
+ assertEquals(0, mBrightnessReason.getModifier());
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 6ba7368f8f26..5294943fa387 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -29,8 +29,10 @@ import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.PowerManager;
import android.provider.DeviceConfig;
+import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
@@ -39,6 +41,7 @@ import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.testutils.TestHandler;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -52,14 +55,15 @@ public class BrightnessClamperControllerTest {
private final TestHandler mTestHandler = new TestHandler(null);
+ @Rule
+ public final TestableContext mMockContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
@Mock
private BrightnessClamperController.ClamperChangeListener mMockExternalListener;
@Mock
private BrightnessClamperController.DisplayDeviceData mMockDisplayDeviceData;
@Mock
- private Context mMockContext;
- @Mock
private DeviceConfigParameterProvider mMockDeviceConfigParameterProvider;
@Mock
private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;
@@ -231,6 +235,13 @@ public class BrightnessClamperControllerTest {
assertEquals(initialSlowChange, state.isSlowChange());
}
+ @Test
+ public void testStop() {
+ mClamperController.stop();
+ verify(mMockModifier).stop();
+ verify(mMockClamper).stop();
+ }
+
private BrightnessClamperController createBrightnessClamperController() {
return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
mMockDisplayDeviceData, mMockContext, mFlags);
@@ -240,14 +251,14 @@ public class BrightnessClamperControllerTest {
private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
mClampers;
- private final List<BrightnessModifier> mModifiers;
+ private final List<BrightnessStateModifier> mModifiers;
private BrightnessClamperController.ClamperChangeListener mCapturedChangeListener;
private TestInjector(
List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>>
clampers,
- List<BrightnessModifier> modifiers) {
+ List<BrightnessStateModifier> modifiers) {
mClampers = clampers;
mModifiers = modifiers;
}
@@ -268,7 +279,8 @@ public class BrightnessClamperControllerTest {
}
@Override
- List<BrightnessModifier> getModifiers(Context context) {
+ List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
+ Handler handler, BrightnessClamperController.ClamperChangeListener listener) {
return mModifiers;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
new file mode 100644
index 000000000000..ac7d1f5ba452
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.clamper
+
+import android.os.PowerManager
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.TestableContext
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.display.brightness.BrightnessReason
+import com.android.server.testutils.TestHandler
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.mock
+
+private const val userId = UserHandle.USER_CURRENT
+
+class BrightnessLowLuxModifierTest {
+
+ private var mockClamperChangeListener =
+ mock<BrightnessClamperController.ClamperChangeListener>()
+
+ val context = TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext())
+
+ private val testHandler = TestHandler(null)
+ private lateinit var modifier: BrightnessLowLuxModifier
+
+ @Before
+ fun setUp() {
+ modifier = BrightnessLowLuxModifier(testHandler, mockClamperChangeListener, context)
+ testHandler.flush()
+ }
+
+ @Test
+ fun testThrottlingBounds() {
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // true
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.7f, userId)
+ modifier.recalculateLowerBound()
+ testHandler.flush()
+ assertThat(modifier.isActive).isTrue()
+
+ // TODO: code currently returns MIN/MAX; update with lux values
+ assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
+ }
+
+ @Test
+ fun testGetReason_UserSet() {
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.7f, userId)
+ modifier.recalculateLowerBound()
+ testHandler.flush()
+ assertThat(modifier.isActive).isTrue()
+
+ // Test restriction from user setting
+ assertThat(modifier.brightnessReason)
+ .isEqualTo(BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
+ }
+
+ @Test
+ fun testGetReason_Lux() {
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.0f, userId)
+ modifier.recalculateLowerBound()
+ testHandler.flush()
+ assertThat(modifier.isActive).isTrue()
+
+ // Test restriction from lux setting
+ assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
new file mode 100644
index 000000000000..b363fd4cc7cb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.mockito.ArgumentMatchers.any;
+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.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SensitiveContentProtectionManagerServiceTest {
+ private static final String NOTIFICATION_KEY_1 = "com.android.server.notification.TEST_KEY_1";
+ private static final String NOTIFICATION_KEY_2 = "com.android.server.notification.TEST_KEY_2";
+
+ private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one";
+ private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two";
+
+ private static final int NOTIFICATION_UID_1 = 5;
+ private static final int NOTIFICATION_UID_2 = 6;
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(getInstrumentation().getTargetContext(), null);
+
+ private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService;
+
+ @Captor
+ ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+
+ @Mock
+ private MediaProjectionManager mProjectionManager;
+
+ @Mock
+ private WindowManagerInternal mWindowManager;
+
+ @Mock
+ private StatusBarNotification mNotification1;
+
+ @Mock
+ private StatusBarNotification mNotification2;
+
+ @Mock
+ private RankingMap mRankingMap;
+
+ @Mock
+ private Ranking mSensitiveRanking;
+
+ @Mock
+ private Ranking mNonSensitiveRanking;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mSensitiveContentProtectionManagerService =
+ new SensitiveContentProtectionManagerService(mContext);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener =
+ spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+
+ // Setup RankingMap and two possilbe rankings
+ when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
+ when(mNonSensitiveRanking.hasSensitiveContent()).thenReturn(false);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager);
+
+ // Obtain useful mMediaProjectionCallback
+ verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
+ }
+
+ @After
+ public void tearDown() {
+ mSensitiveContentProtectionManagerService.onDestroy();
+ }
+
+ private Set<PackageInfo> setupSensitiveNotification() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mNonSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+ }
+
+ private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+ when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_2);
+
+ StatusBarNotification[] mNotifications =
+ new StatusBarNotification[] {mNotification1, mNotification2};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mSensitiveRanking);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+ .thenReturn(mSensitiveRanking);
+
+ return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+ }
+
+ private void setupNoSensitiveNotifications() {
+ // Setup Notification Values
+ when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+ when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+ when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+ StatusBarNotification[] mNotifications = new StatusBarNotification[] {mNotification1};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+ .thenReturn(mNonSensitiveRanking);
+ }
+
+ private void setupNoNotifications() {
+ // Setup Notification Values
+ StatusBarNotification[] mNotifications = new StatusBarNotification[] {};
+ doReturn(mNotifications)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+ }
+
+ @Test
+ public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromSamePackageAndUid();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromDifferentPackage();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages =
+ setupMultipleSensitiveNotificationsFromDifferentUid();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() {
+ setupSensitiveNotification();
+
+ MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ Mockito.reset(mWindowManager);
+
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
+ Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+ MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+ Mockito.reset(mWindowManager);
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getActiveNotificationsThrows_noBlockedPackages() {
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRankingThrows_noBlockedPackages() {
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRanking_nullRankingMap_noBlockedPackages() {
+ doReturn(null)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void mediaProjectionOnStart_getCurrentRanking_missingRanking_noBlockedPackages() {
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+ verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+}
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 e5ecdc478df7..0403c64fc624 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -210,6 +210,9 @@ public class PackageArchiverTest {
anyInt())).thenReturn(1);
when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn(
PackageInstaller.SessionInfo.INVALID_ID);
+ PackageInstallerSession session = mock(PackageInstallerSession.class);
+ when(mInstallerService.getSession(anyInt())).thenReturn(session);
+ when(session.getUnarchivalStatus()).thenReturn(PackageInstaller.UNARCHIVAL_STATUS_UNSET);
doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
.when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
eq(mUserId));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 60cedcfd6dd0..24e7242792fb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -108,6 +108,20 @@ public class PackageMonitorCallbackHelperTest {
}
@Test
+ public void testPackageMonitorCallback_SuspendNoAccessCallbackNotCalled() throws Exception {
+ BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> null;
+
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+ null /* broadcastAllowList */, mHandler, filterExtras);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+ }
+
+ @Test
public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
Bundle result = new Bundle();
result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 9851bc1b4be3..97e94e3c6fc9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,24 +16,24 @@
package com.android.server.trust;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-
import android.Manifest;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
import android.content.BroadcastReceiver;
@@ -45,14 +45,23 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricManager;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.test.TestLooper;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.security.Authorization;
+import android.security.authorization.IKeystoreAuthorization;
import android.service.trust.TrustAgentService;
import android.testing.TestableContext;
import android.view.IWindowManager;
@@ -61,12 +70,11 @@ import android.view.WindowManagerGlobal;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.google.android.collect.Lists;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -74,37 +82,74 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Random;
+import java.util.Collection;
+import java.util.List;
public class TrustManagerServiceTest {
@Rule
- public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .spyStatic(ActivityManager.class)
+ .spyStatic(Authorization.class)
+ .mockStatic(ServiceManager.class)
+ .mockStatic(WindowManagerGlobal.class)
+ .build();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
private static final String URI_SCHEME_PACKAGE = "package";
- private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+ private static final int TEST_USER_ID = 50;
+ private static final int PARENT_USER_ID = 60;
+ private static final int PROFILE_USER_ID = 70;
+ private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
+ private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L };
- private final TestLooper mLooper = new TestLooper();
private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
- private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
- private final TrustManagerService mService = new TrustManagerService(mMockContext);
-
- @Mock
- private PackageManager mPackageManagerMock;
+ private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
+ private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
+
+ private @Mock ActivityManager mActivityManager;
+ private @Mock BiometricManager mBiometricManager;
+ private @Mock DevicePolicyManager mDevicePolicyManager;
+ private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private @Mock PackageManager mPackageManager;
+ private @Mock UserManager mUserManager;
+ private @Mock IWindowManager mWindowManager;
+
+ private HandlerThread mHandlerThread;
+ private TrustManagerService.Injector mInjector;
+ private TrustManagerService mService;
+ private ITrustManager mTrustManager;
@Before
- public void setUp() {
- resetTrustAgentLockSettings();
- LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+ public void setUp() throws Exception {
+ when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
+ doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+
+ doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
+
+ when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
+ when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
+ when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
+ when(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).thenReturn(mEnabledTrustAgents);
+ doAnswer(invocation -> {
+ mKnownTrustAgents.clear();
+ mKnownTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+ return null;
+ }).when(mLockPatternUtils).setKnownTrustAgents(any(), eq(TEST_USER_ID));
+ doAnswer(invocation -> {
+ mEnabledTrustAgents.clear();
+ mEnabledTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+ return null;
+ }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID));
ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
@Override
@@ -112,17 +157,43 @@ public class TrustManagerServiceTest {
return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
}
};
- when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+ when(mPackageManager.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
- when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+ when(mPackageManager.checkPermission(any(), any())).thenReturn(
PackageManager.PERMISSION_GRANTED);
- mMockContext.setMockPackageManager(mPackageManagerMock);
+
+ when(mUserManager.getAliveUsers()).thenReturn(
+ List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+
+ mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+ mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+ mMockContext.setMockPackageManager(mPackageManager);
+ mMockContext.addMockSystemService(UserManager.class, mUserManager);
+ doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+ LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+ grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
+ grantPermission(Manifest.permission.TRUST_LISTENER);
+
+ mHandlerThread = new HandlerThread("handler");
+ mHandlerThread.start();
+ mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
+ mService = new TrustManagerService(mMockContext, mInjector);
+
+ // Get the ITrustManager from the new TrustManagerService.
+ mService.onStart();
+ ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
+ verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
+ binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
+ mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
}
@After
public void tearDown() {
- resetTrustAgentLockSettings();
LocalServices.removeServiceForTest(SystemServiceManager.class);
+ mHandlerThread.quit();
}
@Test
@@ -142,10 +213,9 @@ public class TrustManagerServiceTest {
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1, systemTrustAgent2);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2);
+ assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2,
+ userTrustAgent1, userTrustAgent2);
}
@Test
@@ -162,10 +232,8 @@ public class TrustManagerServiceTest {
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent, defaultTrustAgent);
+ assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+ assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent, defaultTrustAgent);
}
@Test
@@ -174,16 +242,16 @@ public class TrustManagerServiceTest {
"com.android/.SystemTrustAgent");
ComponentName trustAgent2 = ComponentName.unflattenFromString(
"com.android/.AnotherSystemTrustAgent");
- initializeEnabledAgents(trustAgent1);
+ mEnabledTrustAgents.add(trustAgent1);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
addTrustAgent(trustAgent1, /* isSystemApp= */ true);
addTrustAgent(trustAgent2, /* isSystemApp= */ true);
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(trustAgent1);
+ assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
}
@Test
@@ -192,17 +260,17 @@ public class TrustManagerServiceTest {
"com.android/.SystemTrustAgent");
ComponentName trustAgent2 = ComponentName.unflattenFromString(
"com.android/.AnotherSystemTrustAgent");
- initializeEnabledAgents(trustAgent1);
- initializeKnownAgents(trustAgent1);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+ Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+ Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
addTrustAgent(trustAgent1, /* isSystemApp= */ true);
addTrustAgent(trustAgent2, /* isSystemApp= */ true);
bootService();
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- trustAgent1, trustAgent2);
+ assertThat(mEnabledTrustAgents).containsExactly(trustAgent1, trustAgent2);
+ assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
}
@Test
@@ -214,10 +282,8 @@ public class TrustManagerServiceTest {
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
+ assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
+ assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
}
@Test
@@ -235,10 +301,8 @@ public class TrustManagerServiceTest {
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent);
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- defaultTrustAgent, newAgentComponentName);
+ assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+ assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
}
@Test
@@ -250,9 +314,8 @@ public class TrustManagerServiceTest {
mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
- assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
- newAgentComponentName);
+ assertThat(mEnabledTrustAgents).isEmpty();
+ assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
}
@Test
@@ -265,50 +328,88 @@ public class TrustManagerServiceTest {
addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
bootService();
// Simulate user turning off systemTrustAgent2
- mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
- TEST_USER_ID);
+ mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
- assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
- systemTrustAgent1);
+ assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
}
@Test
public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException {
- final LockPatternUtils utils = mock(LockPatternUtils.class);
- final TrustManagerService service = new TrustManagerService(mMockContext,
- new TrustManagerService.Injector(utils, mLooper.getLooper()));
final ITrustListener trustListener = mock(ITrustListener.class);
- final IWindowManager windowManager = mock(IWindowManager.class);
- final int userId = new Random().nextInt();
+ mTrustManager.registerTrustListener(trustListener);
+ mService.waitForIdle();
+ mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID);
+ mService.waitForIdle();
+ verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
+ }
- mMockContext.getTestablePermissions().setPermission(Manifest.permission.TRUST_LISTENER,
- PERMISSION_GRANTED);
+ // Tests that when the device is locked for a managed profile with a *unified* challenge, the
+ // device locked notification that is sent to Keystore contains the biometric SIDs of the parent
+ // user, not the profile. This matches the authentication that is needed to unlock the device
+ // for the profile again.
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testLockDeviceForManagedProfileWithUnifiedChallenge_usesParentBiometricSids()
+ throws Exception {
+ setupMocksForProfile(/* unifiedChallenge= */ true);
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ }
+
+ // Tests that when the device is locked for a managed profile with a *separate* challenge, the
+ // device locked notification that is sent to Keystore contains the biometric SIDs of the
+ // profile itself. This matches the authentication that is needed to unlock the device for the
+ // profile again.
+ @Test
+ public void testLockDeviceForManagedProfileWithSeparateChallenge_usesProfileBiometricSids()
+ throws Exception {
+ setupMocksForProfile(/* unifiedChallenge= */ false);
- when(utils.getKnownTrustAgents(anyInt())).thenReturn(new ArrayList<>());
+ mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
+ verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
- MockitoSession mockSession = mockitoSession()
- .initMocks(this)
- .mockStatic(ServiceManager.class)
- .mockStatic(WindowManagerGlobal.class)
- .startMocking();
+ mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
+ verify(mKeystoreAuthorization)
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+ }
- doReturn(windowManager).when(() -> {
- WindowManagerGlobal.getWindowManagerService();
- });
+ private void setupMocksForProfile(boolean unifiedChallenge) {
+ UserInfo parent = new UserInfo(PARENT_USER_ID, "parent", UserInfo.FLAG_FULL);
+ UserInfo profile = new UserInfo(PROFILE_USER_ID, "profile", UserInfo.FLAG_MANAGED_PROFILE);
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(parent, profile));
+ when(mUserManager.getUserInfo(PARENT_USER_ID)).thenReturn(parent);
+ when(mUserManager.getUserInfo(PROFILE_USER_ID)).thenReturn(profile);
+ when(mUserManager.getProfileParent(PROFILE_USER_ID)).thenReturn(parent);
+ when(mUserManager.getEnabledProfileIds(PARENT_USER_ID))
+ .thenReturn(new int[] { PROFILE_USER_ID });
+
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ when(mLockPatternUtils.isProfileWithUnifiedChallenge(PROFILE_USER_ID))
+ .thenReturn(unifiedChallenge);
+ when(mLockPatternUtils.isManagedProfileWithUnifiedChallenge(PROFILE_USER_ID))
+ .thenReturn(unifiedChallenge);
+ when(mLockPatternUtils.isSeparateProfileChallengeEnabled(PROFILE_USER_ID))
+ .thenReturn(!unifiedChallenge);
+
+ when(mBiometricManager.getAuthenticatorIds(PARENT_USER_ID))
+ .thenReturn(PARENT_BIOMETRIC_SIDS);
+ when(mBiometricManager.getAuthenticatorIds(PROFILE_USER_ID))
+ .thenReturn(PROFILE_BIOMETRIC_SIDS);
- service.onStart();
- ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
- verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
- binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
- ITrustManager manager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
- manager.registerTrustListener(trustListener);
- mLooper.dispatchAll();
- manager.reportEnabledTrustAgentsChanged(userId);
- mLooper.dispatchAll();
- verify(trustListener).onEnabledTrustAgentsChanged(eq(userId));
- mockSession.finishMocking();
+ bootService();
+ mService.onUserSwitching(null, new SystemService.TargetUser(parent));
}
private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
@@ -327,27 +428,16 @@ public class TrustManagerServiceTest {
mTrustAgentResolveInfoList.add(resolveInfo);
}
- private void initializeEnabledAgents(ComponentName... enabledAgents) {
- mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
- Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
- Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
- }
-
- private void initializeKnownAgents(ComponentName... knownAgents) {
- mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
- Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
- Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
- }
-
private void bootService() {
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mMockContext.sendUserStartedBroadcast();
}
- private void resetTrustAgentLockSettings() {
- mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
- mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+ private void grantPermission(String permission) {
+ mMockContext.getTestablePermissions().setPermission(
+ permission, PackageManager.PERMISSION_GRANTED);
}
/** A mock Context that allows the test process to send protected broadcasts. */
@@ -355,6 +445,8 @@ public class TrustManagerServiceTest {
private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
new ArrayList<>();
+ private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
+ new ArrayList<>();
MockContext(Context base) {
super(base);
@@ -369,10 +461,18 @@ public class TrustManagerServiceTest {
if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
mPackageChangedBroadcastReceivers.add(receiver);
}
+ if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
+ mUserStartedBroadcastReceivers.add(receiver);
+ }
return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ @Nullable String receiverPermission, @Nullable Bundle options) {
+ }
+
void sendPackageChangedBroadcast(ComponentName changedComponent) {
Intent intent = new Intent(
Intent.ACTION_PACKAGE_CHANGED,
@@ -386,5 +486,13 @@ public class TrustManagerServiceTest {
receiver.onReceive(this, intent);
}
}
+
+ void sendUserStartedBroadcast() {
+ Intent intent = new Intent(Intent.ACTION_USER_STARTED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+ for (BroadcastReceiver receiver : mUserStartedBroadcastReceivers) {
+ receiver.onReceive(this, intent);
+ }
+ }
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 9b8419021c5c..00450267ee79 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -215,6 +215,16 @@ java_library {
}
java_library {
+ name: "mockito-test-utils",
+ srcs: [
+ "utils-mockito/**/*.kt",
+ ],
+ static_libs: [
+ "mockito-target-minus-junit4",
+ ],
+}
+
+java_library {
name: "servicestests-utils-mockito-extended",
srcs: [
"utils/**/*.java",
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
new file mode 100644
index 000000000000..523c5c060cf5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.AndroidTestCase;
+
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
+
+public class BootReceiverTest extends AndroidTestCase {
+ private static final String TAG = "BootReceiverTest";
+
+ public void testRemoveMemoryFromTombstone() {
+ Tombstone tombstoneBase = Tombstone.newBuilder()
+ .setBuildFingerprint("build_fingerprint")
+ .setRevision("revision")
+ .setPid(123)
+ .setTid(23)
+ .setUid(34)
+ .setSelinuxLabel("selinux_label")
+ .addCommandLine("cmd1")
+ .addCommandLine("cmd2")
+ .addCommandLine("cmd3")
+ .setProcessUptime(300)
+ .setAbortMessage("abort")
+ .addCauses(TombstoneProtos.Cause.newBuilder()
+ .setHumanReadable("cause1")
+ .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
+ .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
+ .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
+ .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
+ TombstoneProtos.LogMessage.newBuilder()
+ .setTimestamp("123")
+ .setMessage("message")))
+ .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
+ .build();
+
+ Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
+ .putThreads(1, TombstoneProtos.Thread.newBuilder()
+ .setId(1)
+ .setName("thread1")
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+ .addBacktraceNote("backtracenote1")
+ .addUnreadableElfFiles("files1")
+ .setTaggedAddrCtrl(1)
+ .setPacEnabledKeys(10)
+ .build())
+ .build();
+
+ Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
+ .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
+ .setBeginAddress(1)
+ .setEndAddress(100)
+ .setOffset(10)
+ .setRead(true)
+ .setWrite(true)
+ .setExecute(false)
+ .setMappingName("mapping")
+ .setBuildId("build")
+ .setLoadBias(70))
+ .putThreads(1, TombstoneProtos.Thread.newBuilder()
+ .setId(1)
+ .setName("thread1")
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+ .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+ .addBacktraceNote("backtracenote1")
+ .addUnreadableElfFiles("files1")
+ .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
+ .setRegisterName("register1")
+ .setMappingName("mapping")
+ .setBeginAddress(10))
+ .setTaggedAddrCtrl(1)
+ .setPacEnabledKeys(10)
+ .build())
+ .build();
+
+ assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
+ .isEqualTo(tombstoneWithoutMemory);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 995d1f4d5520..276c8321fb65 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -1982,8 +1982,8 @@ public class VirtualDeviceManagerServiceTest {
return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
/* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
/* associatedDevice= */ null, /* selfManaged= */ true,
- /* notifyOnDeviceNearby= */ false, /* revoked= */false, /* timeApprovedMs= */0,
- /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+ /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
+ /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
}
/** Helper class to drop permissions temporarily and restore them at the end of a test. */
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index a2f8c8bbe13e..6b85a327e61b 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,10 +25,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -1223,35 +1221,6 @@ public class InputMethodUtilsTest {
StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
}
- @Test
- public void testInputMethodSettings_SwitchCurrentUser() {
- TestContext ownerUserContext = createMockContext(0 /* userId */);
- final InputMethodInfo systemIme = createFakeInputMethodInfo(
- "SystemIme", "fake.latin", true /* isSystem */);
- final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
- "fake.voice0", false /* isSystem */);
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- methodMap.put(systemIme.getId(), systemIme);
-
- // Init InputMethodSettings for the owner user (userId=0), verify calls can get the
- // corresponding user's context, contentResolver and the resources configuration.
- InputMethodUtils.InputMethodSettings settings = new InputMethodUtils.InputMethodSettings(
- methodMap, 0 /* userId */);
- assertEquals(0, settings.getCurrentUserId());
-
- settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
- verify(ownerUserContext.getResources(), atLeastOnce()).getConfiguration();
-
- // Calling switchCurrentUser to the secondary user (userId=10), verify calls can get the
- // corresponding user's context, contentResolver and the resources configuration.
- settings.switchCurrentUser(10 /* userId */);
- assertEquals(10, settings.getCurrentUserId());
-
- settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
- verify(TestContext.getSecondaryUserContext().getResources(),
- atLeastOnce()).getConfiguration();
- }
-
private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
final IntArray subtypes = new IntArray();
final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 6a088d99588e..757abde2041e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -157,7 +157,7 @@ class AndroidPackageParsingValidationTest {
validateTagCount("uses-library", 1000, tag)
validateTagCount("activity-alias", 4000, tag)
validateTagCount("provider", 8000, tag)
- validateTagCount("activity", 40000, tag)
+ validateTagCount("activity", 30000, tag)
}
@Test
@@ -465,7 +465,8 @@ class AndroidPackageParsingValidationTest {
R.styleable.AndroidManifestData_pathAdvancedPattern,
4000
)
- validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
+ validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 255)
+ validateTagAttr(tag, "mimeGroup", R.styleable.AndroidManifestData_mimeGroup, 1024)
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index cf8548cfe689..1b77b99e7d3e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -19,17 +19,21 @@ import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.GROUP_ALERT_ALL;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -195,7 +199,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// TODO (b/291907312): remove feature flag
mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Disable feature flags by default. Tests should enable as needed.
- mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
+ Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
mNotificationInstanceIdSequence));
@@ -364,10 +369,20 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
private NotificationRecord getNotificationRecord(int id,
- boolean insistent, boolean once,
- boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
- boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
- boolean isLeanback, UserHandle userHandle) {
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, UserHandle userHandle) {
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
+ defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle,
+ mPkg);
+ }
+
+ private NotificationRecord getNotificationRecord(int id,
+ boolean insistent, boolean once,
+ boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+ boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+ boolean isLeanback, UserHandle userHandle, String packageName) {
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
@@ -427,8 +442,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
.thenReturn(isLeanback);
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
- mPid, n, userHandle, null, System.currentTimeMillis());
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+ mUid, mPid, n, userHandle, null, System.currentTimeMillis());
NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
mService.addNotification(r);
return r;
@@ -1990,7 +2005,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
public void testBeepVolume_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2015,13 +2029,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
- // TODO b/270456865: Only one of the two strategies will be released.
- // The other one need to be removed
@Test
- public void testBeepVolume_politeNotif_Strategy2() throws Exception {
+ public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2032,14 +2044,58 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
Mockito.reset(mRingtonePlayer);
- // update should beep at 0% volume
- r.isUpdate = true;
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ // Use different package for next notifications
+ NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // update should beep at 50% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ // Use different package for next notifications
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");
+
+ // 2nd update should beep at 0% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
verifyBeepVolume(0.0f);
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // Use package with user-set sounds for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+ mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+ NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // update should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
// 2nd update should beep at 50% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
verifyBeepVolume(0.5f);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
@@ -2047,39 +2103,101 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
- public void testVibrationIntensity_politeNotif() throws Exception {
+ public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
initAttentionHelper(flagResolver);
- NotificationRecord r = getBuzzyBeepyNotification();
+ NotificationRecord r = getBeepyNotification();
// set up internal state
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
- VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
- Mockito.reset(vibratorHelper);
+ // Use different channel for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
- // update should buzz at 50% intensity
- r.isUpdate = true;
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ // update should beep at 50% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
- Mockito.reset(vibratorHelper);
+ // 2nd update should beep at 0% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.0f);
- // 2nd update should buzz at 0% intensity
+ // Use different package for next notifications
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // Update from new package should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_applyPerApp_ChannelHasUserSound() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // set up internal state
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+ Mockito.reset(mRingtonePlayer);
+
+ // Use different channel for next notifications
+ mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+ mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+
+ // update should beep at 100% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 50% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(0.5f);
+
+ // Use different package for next notifications
+ mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
+ NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+ false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+ // Update from new package should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
- public void testVibrationIntensity_politeNotif_Strategy2() throws Exception {
+ public void testVibrationIntensity_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
@@ -2092,21 +2210,22 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
Mockito.reset(vibratorHelper);
- // update should buzz at 0% intensity
+ // update should buzz at 50% intensity
r.isUpdate = true;
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+ verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
Mockito.reset(vibratorHelper);
- // 2nd update should buzz at 50% intensity
+ // 2nd update should buzz at 0% intensity
mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
+ verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
}
@Test
public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
TestableFlagResolver flagResolver = new TestableFlagResolver();
// When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled
@@ -2161,7 +2280,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
public void testBeepVolume_politeNotif_workProfile() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
@@ -2202,7 +2320,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
- flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 5892793fdb72..c10c3c28e9dd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -170,6 +170,10 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
Settings.Secure.putIntForUser(getContext().getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
+ // fake cloned settings to profile
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, mProfileId);
+ mHistoryManager.mSettingsObserver.update(null, mProfileId);
// unlock user, verify that history is disabled for self and profile
mHistoryManager.onUserUnlocked(USER_SYSTEM);
@@ -181,6 +185,37 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
}
@Test
+ public void testAddProfile_historyEnabledInPrimary() {
+ // create a history
+ mHistoryManager.onUserUnlocked(MIN_SECONDARY_USER_ID);
+ assertThat(mHistoryManager.doesHistoryExistForUser(MIN_SECONDARY_USER_ID)).isTrue();
+
+ // fake Settings#CLONE_TO_MANAGED_PROFILE
+ int newProfileId = 99;
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1, newProfileId);
+ mUsers = new ArrayList<>();
+ UserInfo userFullSecondary = new UserInfo();
+ userFullSecondary.id = MIN_SECONDARY_USER_ID;
+ mUsers.add(userFullSecondary);
+ UserInfo userProfile = new UserInfo();
+ userProfile.id = newProfileId;
+ mUsers.add(userProfile);
+ when(mUserManager.getUsers()).thenReturn(mUsers);
+
+ mProfiles = new int[] {MIN_SECONDARY_USER_ID, userProfile.id};
+ when(mUserManager.getProfileIds(MIN_SECONDARY_USER_ID, true)).thenReturn(mProfiles);
+ when(mUserManager.getProfileIds(userProfile.id, true))
+ .thenReturn(new int[] {userProfile.id});
+ when(mUserManager.getProfileParent(userProfile.id)).thenReturn(userFullSecondary);
+
+ // add profile
+ mHistoryManager.onUserAdded(newProfileId);
+ mHistoryManager.onUserUnlocked(newProfileId);
+ assertThat(mHistoryManager.doesHistoryExistForUser(newProfileId)).isTrue();
+ }
+
+ @Test
public void testOnUserUnlocked_historyDisabledThenEnabled() {
// create a history
mHistoryManager.onUserUnlocked(USER_SYSTEM);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f1edd9a59b99..c1f35ccb69e0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -958,22 +958,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
- private void setUpPrefsForHistory(int uid, boolean globalEnabled) throws Exception {
+ private void setUpPrefsForHistory(@UserIdInt int userId, boolean globalEnabled)
+ throws Exception {
initNMS(SystemService.PHASE_ACTIVITY_MANAGER_READY);
// Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, userId);
// Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0);
+ setUsers(new int[] {0, userId});
// Forces an update by calling observe on mSettingsObserver, which picks up the settings
// changes above.
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0);
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, userId) != 0);
}
private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
@@ -10443,7 +10445,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testHandleOnPackageRemoved_ClearsHistory() throws Exception {
// Enables Notification History setting
- setUpPrefsForHistory(mUid, true /* =enabled */);
+ setUpPrefsForHistory(mUserId, true /* =enabled */);
// Posts a notification to the mTestNotificationChannel.
final NotificationRecord notif = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 999e33c24322..3d8ec2ec9277 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -18,19 +18,31 @@ package com.android.server.notification;
import static com.google.common.truth.Truth.assertThat;
+import android.app.Flags;
import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ZenDeviceEffectsTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public final void setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ }
+
@Test
public void builder() {
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
@@ -40,6 +52,7 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
.setShouldMaximizeDoze(true)
.setShouldUseNightMode(false)
.setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
+ .setUserModifiedFields(8)
.build();
assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -52,6 +65,7 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
assertThat(deviceEffects.shouldUseNightMode()).isFalse();
assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
+ assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
}
@Test
@@ -83,6 +97,7 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
.setShouldMinimizeRadioUsage(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
+ .setUserModifiedFields(6)
.build();
Parcel parcel = Parcel.obtain();
@@ -101,6 +116,7 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
assertThat(copy.shouldUseNightMode()).isTrue();
assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
assertThat(copy.shouldDisplayGrayscale()).isFalse();
+ assertThat(copy.getUserModifiedFields()).isEqualTo(6);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 3185c50c27ef..177d64555899 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -19,12 +19,15 @@ package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
+import android.app.AutomaticZenRule;
import android.app.Flags;
import android.app.NotificationManager.Policy;
import android.content.ComponentName;
@@ -46,6 +49,7 @@ import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -83,6 +87,11 @@ public class ZenModeConfigTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Before
+ public final void setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ }
+
@Test
public void testPriorityOnlyMutingAllNotifications() {
ZenModeConfig config = getMutedRingerConfig();
@@ -275,6 +284,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
assertEquals(expected.getAllowedChannels(), actual.getAllowedChannels());
+ assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields());
}
@Test
@@ -327,6 +337,53 @@ public class ZenModeConfigTest extends UiServiceTestCase {
}
@Test
+ public void testCanBeUpdatedByApp_nullPolicyAndDeviceEffects() throws Exception {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.zenPolicy = null;
+ rule.zenDeviceEffects = null;
+
+ assertThat(rule.canBeUpdatedByApp()).isTrue();
+
+ rule.userModifiedFields = 1;
+ assertThat(rule.canBeUpdatedByApp()).isFalse();
+ }
+
+ @Test
+ public void testCanBeUpdatedByApp_policyModified() throws Exception {
+ ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
+ ZenPolicy policy = policyBuilder.build();
+
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.zenPolicy = policy;
+
+ assertThat(rule.userModifiedFields).isEqualTo(0);
+ assertThat(rule.canBeUpdatedByApp()).isTrue();
+
+ policy = policyBuilder.setUserModifiedFields(1).build();
+ assertThat(policy.getUserModifiedFields()).isEqualTo(1);
+ rule.zenPolicy = policy;
+ assertThat(rule.canBeUpdatedByApp()).isFalse();
+ }
+
+ @Test
+ public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
+ ZenDeviceEffects.Builder deviceEffectsBuilder =
+ new ZenDeviceEffects.Builder().setUserModifiedFields(0);
+ ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
+
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.zenDeviceEffects = deviceEffects;
+
+ assertThat(rule.userModifiedFields).isEqualTo(0);
+ assertThat(rule.canBeUpdatedByApp()).isTrue();
+
+ deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
+ assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
+ rule.zenDeviceEffects = deviceEffects;
+ assertThat(rule.canBeUpdatedByApp()).isFalse();
+ }
+
+ @Test
public void testWriteToParcel() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
@@ -347,6 +404,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
+ rule.userModifiedFields = 16;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
@@ -371,6 +429,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.allowManualInvocation, parceled.allowManualInvocation);
assertEquals(rule.iconResName, parceled.iconResName);
assertEquals(rule.type, parceled.type);
+ assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
assertEquals(rule.triggerDescription, parceled.triggerDescription);
assertEquals(rule.zenPolicy, parceled.zenPolicy);
assertEquals(rule, parceled);
@@ -448,6 +507,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
+ rule.userModifiedFields = 4;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
@@ -476,6 +536,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
assertEquals(rule.type, fromXml.type);
+ assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
assertEquals(rule.triggerDescription, fromXml.triggerDescription);
assertEquals(rule.iconResName, fromXml.iconResName);
}
@@ -550,6 +611,22 @@ public class ZenModeConfigTest extends UiServiceTestCase {
}
@Test
+ public void testRuleXml_userModifiedField() throws Exception {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
+ assertThat(rule.userModifiedFields).isEqualTo(1);
+ assertThat(rule.canBeUpdatedByApp()).isFalse();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeRuleXml(rule, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+ assertThat(fromXml.userModifiedFields).isEqualTo(rule.userModifiedFields);
+ assertThat(fromXml.canBeUpdatedByApp()).isFalse();
+ }
+
+ @Test
public void testZenPolicyXml_classic() throws Exception {
ZenPolicy policy = new ZenPolicy.Builder()
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
@@ -615,6 +692,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
.allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
.hideAllVisualEffects()
.showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
+ .setUserModifiedFields(4)
.build();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -649,6 +727,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
assertEquals(policy.getVisualEffectNotificationList(),
fromXml.getVisualEffectNotificationList());
+ assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields());
}
private ZenModeConfig getMutedRingerConfig() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 93cd44eb7966..7e92e427b9a4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -76,7 +76,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
? Set.of()
: Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
- RuleDiff.FIELD_ZEN_DEVICE_EFFECTS);
+ RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_USER_MODIFIED_FIELDS);
// allowPriorityChannels is flagged by android.app.modes_api
public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
@@ -304,6 +304,7 @@ public class ZenModeDiffTest extends UiServiceTestCase {
rule.zenDeviceEffects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
+ rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
}
return rule;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index f84d8e95e426..9eed974c701f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -21,6 +21,9 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
@@ -2197,7 +2200,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- public void addAutomaticZenRule_fromUser_respectsHiddenEffects() {
+ public void addAutomaticZenRule_fromUser_respectsHiddenEffects() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
@@ -2222,7 +2225,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
"reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
+
+ // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields.
+ // So we clear before comparing.
+ ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
+ .setUserModifiedFields(0).build();
+
+ assertThat(savedEffects).isEqualTo(zde);
}
@Test
@@ -2298,8 +2307,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
- .setShouldUseNightMode(true) // Good
- .setShouldMaximizeDoze(true) // Also good
+ .setShouldUseNightMode(true)
+ .setShouldMaximizeDoze(true)
+ // Just to emphasize that unset values default to false;
+ // even with this line removed, tap to wake would be set to false.
+ .setShouldDisableTapToWake(false)
.build();
mZenModeHelper.updateAutomaticZenRule(ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2308,7 +2320,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
UPDATE_ORIGIN_USER, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
+
+ // savedRule.getDeviceEffects() is equal to updateFromUser, except for the
+ // userModifiedFields, so we clear before comparing.
+ ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
+ .setUserModifiedFields(0).build();
+
+ assertThat(savedEffects).isEqualTo(updateFromUser);
}
@Test
@@ -3321,6 +3339,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
+ rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
@@ -3335,6 +3354,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(POLICY, actual.getZenPolicy());
assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity());
assertEquals(TYPE, actual.getType());
+ assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields());
assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed());
assertEquals(CREATION_TIME, actual.getCreationTime());
assertEquals(OWNER.getPackageName(), actual.getPackageName());
@@ -3376,10 +3396,480 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(ALLOW_MANUAL, rule.allowManualInvocation);
assertEquals(OWNER.getPackageName(), rule.getPkg());
assertEquals(ICON_RES_NAME, rule.iconResName);
+ // Because the origin of the update is the app, we don't expect the bitmask to change.
+ assertEquals(0, rule.userModifiedFields);
assertEquals(TRIGGER_DESC, rule.triggerDescription);
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() {
+ // Add a starting rule with the name OriginalName.
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // Checks the name can be changed by the app because the user has not modified it.
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setName("NewName")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.getName()).isEqualTo("NewName");
+ assertThat(rule.canUpdate()).isTrue();
+
+ // The user modifies some other field in the rule, which makes the rule as a whole not
+ // app modifiable.
+ azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.getUserModifiedFields())
+ .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+ assertThat(rule.canUpdate()).isFalse();
+
+ // ...but the app can still modify the name, because the name itself hasn't been modified
+ // by the user.
+ azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setName("NewAppName")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.getName()).isEqualTo("NewAppName");
+
+ // The user modifies the name.
+ azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setName("UserProvidedName")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.getName()).isEqualTo("UserProvidedName");
+ assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME
+ | AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+
+ // The app is no longer able to modify the name.
+ azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setName("NewAppName")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.getName()).isEqualTo("UserProvidedName");
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setZenPolicy(new ZenPolicy.Builder().build())
+ .setDeviceEffects(new ZenDeviceEffects.Builder().build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // Modifies the zen policy and device effects
+ ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
+ .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .build();
+ ZenDeviceEffects deviceEffects =
+ new ZenDeviceEffects.Builder(rule.getDeviceEffects())
+ .setShouldDisplayGrayscale(true)
+ .build();
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(policy)
+ .setDeviceEffects(deviceEffects)
+ .build();
+
+ // Update the rule with the AZR from origin user.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // UPDATE_ORIGIN_USER should change the bitmask and change the values.
+ assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+ assertThat(rule.getUserModifiedFields())
+ .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+ assertThat(rule.getZenPolicy().getUserModifiedFields())
+ .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
+ assertThat(rule.getZenPolicy().getAllowedChannels())
+ .isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(rule.getDeviceEffects().getUserModifiedFields())
+ .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
+ assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(false)
+ .build())
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(false)
+ .build())
+ .build();
+ // Adds the rule using the user, to set user-modified bits.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.canUpdate()).isFalse();
+ assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME);
+
+ ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
+ .allowReminders(true)
+ .build();
+ ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects())
+ .setShouldDisplayGrayscale(true)
+ .build();
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(policy)
+ .setDeviceEffects(deviceEffects)
+ .build();
+
+ // Attempts to update the rule with the AZR from origin init user.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason",
+ Process.SYSTEM_UID);
+ AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified.
+ // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR.
+ assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
+ assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
+ assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+ rule.getZenPolicy().getUserModifiedFields());
+ assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
+ assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+ rule.getDeviceEffects().getUserModifiedFields());
+ assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+
+ // Creates a new rule with the AZR from origin init user.
+ String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+
+ // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new,
+ // but does not update the bitmask.
+ assertThat(newRule.getUserModifiedFields()).isEqualTo(0);
+ assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
+ assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
+ assertThat(newRule.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
+ assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(false)
+ .build())
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(false)
+ .build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // Modifies the zen policy and device effects
+ ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
+ .allowReminders(true)
+ .build();
+ ZenDeviceEffects deviceEffects =
+ new ZenDeviceEffects.Builder(rule.getDeviceEffects())
+ .setShouldDisplayGrayscale(true)
+ .build();
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(policy)
+ .setDeviceEffects(deviceEffects)
+ .build();
+
+ // Update the rule with the AZR from origin systemUI.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "reason", Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
+ assertThat(rule.getUserModifiedFields()).isEqualTo(0);
+ assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
+ assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
+ assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(false)
+ .build())
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(false)
+ .build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.canUpdate()).isTrue();
+
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .allowReminders(true)
+ .build();
+ ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setZenPolicy(policy)
+ .setDeviceEffects(deviceEffects)
+ .build();
+
+ // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule.
+ // The bitmask is not modified.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason",
+ Process.SYSTEM_UID);
+ AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
+ assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+ assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+ rule.getZenPolicy().getUserModifiedFields());
+ assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+ rule.getDeviceEffects().getUserModifiedFields());
+ assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ // Creates another rule, this time from user. This will have user modified bits set.
+ String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+ assertThat(ruleUser.canUpdate()).isFalse();
+
+ // Zen rule update coming from unknown origin. This cannot fully update the rule, because
+ // the rule is already considered user modified.
+ mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN,
+ "reason", Process.SYSTEM_UID);
+ ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+
+ // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified,
+ // so the rule is not changed, and neither is the bitmask.
+ assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
+ // Interruption Filter All is the default value, so it's not included as a modified field.
+ assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0);
+ assertThat(ruleUser.getZenPolicy().getUserModifiedFields()
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0);
+ assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(ruleUser.getDeviceEffects().getUserModifiedFields()
+ | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0);
+ assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowReminders(true)
+ .build())
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build())
+ .build();
+ // Adds the rule using origin unknown, to show that a new rule is always allowed.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // The values are modified but the bitmask is not.
+ assertThat(rule.canUpdate()).isTrue();
+ assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
+ .isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setDeviceEffects(new ZenDeviceEffects.Builder().build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
+ // Sets Device Effects to null
+ .setDeviceEffects(null)
+ .build();
+
+ // Zen rule update coming from unknown origin, but since the rule isn't already
+ // user modified, it can be updated.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // When AZR's ZenDeviceEffects is null, the updated rule's device effects will be null.
+ assertThat(rule.getDeviceEffects()).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_nullPolicyUpdate() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setZenPolicy(new ZenPolicy.Builder().build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.canUpdate()).isTrue();
+
+ AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
+ // Set zen policy to null
+ .setZenPolicy(null)
+ .build();
+
+ // Zen rule update coming from unknown origin, but since the rule isn't already
+ // user modified, it can be updated.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // When AZR's ZenPolicy is null, we expect the updated rule's policy to be null.
+ assertThat(rule.getZenPolicy()).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_nullToNonNullPolicyUpdate() {
+ when(mContext.checkCallingPermission(anyString()))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setZenPolicy(null)
+ // .setDeviceEffects(new ZenDeviceEffects.Builder().build())
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.canUpdate()).isTrue();
+
+ // Create a fully populated ZenPolicy.
+ ZenPolicy policy = new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE) // Differs from the default
+ .allowReminders(true) // Differs from the default
+ .allowEvents(true) // Differs from the default
+ .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT)
+ .allowMessages(PEOPLE_TYPE_STARRED)
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowRepeatCallers(true)
+ .allowAlarms(true)
+ .allowMedia(true)
+ .allowSystem(true) // Differs from the default
+ .showFullScreenIntent(true) // Differs from the default
+ .showLights(true) // Differs from the default
+ .showPeeking(true) // Differs from the default
+ .showStatusBarIcons(true)
+ .showBadges(true)
+ .showInAmbientDisplay(true) // Differs from the default
+ .showInNotificationList(true)
+ .build();
+ AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
+ .setZenPolicy(policy)
+ .build();
+
+ // Applies the update to the rule.
+ // Default config defined in getDefaultConfigParser() is used as the original rule.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // New ZenPolicy differs from the default config
+ assertThat(rule.getZenPolicy()).isNotNull();
+ assertThat(rule.getZenPolicy().getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+ assertThat(rule.canUpdate()).isFalse();
+ assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+ ZenPolicy.FIELD_ALLOW_CHANNELS
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
+ | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM
+ | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT
+ | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS
+ | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK
+ | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT
+ );
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void automaticZenRuleToZenRule_nullToNonNullDeviceEffectsUpdate() {
+ // Adds a starting rule with empty zen policies and device effects
+ AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
+ .setDeviceEffects(null)
+ .build();
+ // Adds the rule using the app, to avoid having any user modified bits set.
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ assertThat(rule.canUpdate()).isTrue();
+
+ ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ AutomaticZenRule azr = new AutomaticZenRule.Builder(rule)
+ .setDeviceEffects(deviceEffects)
+ .build();
+
+ // Applies the update to the rule.
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+
+ // New ZenDeviceEffects is used; all fields considered set, since previously were null.
+ assertThat(rule.getDeviceEffects()).isNotNull();
+ assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ assertThat(rule.canUpdate()).isFalse();
+ assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+ ZenDeviceEffects.FIELD_GRAYSCALE);
+ }
+
+ @Test
public void testUpdateAutomaticRule_disabled_triggersBroadcast() throws Exception {
setupZenConfig();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 2f4f891ce982..21c96d6adc7e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -34,6 +34,7 @@ import com.android.server.UiServiceTestCase;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,6 +50,11 @@ public class ZenPolicyTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Before
+ public final void setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ }
+
@Test
public void testZenPolicyApplyAllowedToDisallowed() {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
@@ -640,6 +646,54 @@ public class ZenPolicyTest extends UiServiceTestCase {
}
@Test
+ public void testFromParcel() {
+ ZenPolicy.Builder builder = new ZenPolicy.Builder();
+ builder.setUserModifiedFields(10);
+
+ ZenPolicy policy = builder.build();
+ assertThat(policy.getUserModifiedFields()).isEqualTo(10);
+
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
+ assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10);
+ }
+
+ @Test
+ public void testPolicy_userModifiedFields() {
+ ZenPolicy.Builder builder = new ZenPolicy.Builder();
+ builder.setUserModifiedFields(10);
+ assertThat(builder.build().getUserModifiedFields()).isEqualTo(10);
+
+ builder.setUserModifiedFields(0);
+ assertThat(builder.build().getUserModifiedFields()).isEqualTo(0);
+ }
+
+ @Test
+ public void testPolicyBuilder_constructFromPolicy() {
+ ZenPolicy.Builder builder = new ZenPolicy.Builder();
+ ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false)
+ .showLights(true).showBadges(false)
+ .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
+ .setUserModifiedFields(20).build();
+
+ ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build();
+
+ assertThat(newPolicy.getPriorityCategoryAlarms()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(newPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(newPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(ZenPolicy.STATE_ALLOW);
+
+ assertThat(newPolicy.getVisualEffectLights()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
+
+ assertThat(newPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+ assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20);
+ }
+
+ @Test
public void testTooLongLists_fromParcel() {
ArrayList<Integer> longList = new ArrayList<Integer>(50);
for (int i = 0; i < 50; i++) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
new file mode 100644
index 000000000000..c3da903c9ef1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH;
+import static android.os.PowerManager.WAKE_REASON_LID;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
+import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.KeyEvent.KEYCODE_HOME;
+import static android.view.KeyEvent.KEYCODE_POWER;
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
+import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.BooleanSupplier;
+/**
+ * Test class for {@link WindowWakeUpPolicy}.
+ *
+ * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests
+ */
+public final class WindowWakeUpPolicyTests {
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock PowerManager mPowerManager;
+ @Mock Clock mClock;
+ @Mock WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
+ private Context mContextSpy;
+ private Resources mResourcesSpy;
+
+ private WindowWakeUpPolicy mPolicy;
+
+ @Before
+ public void setUp() {
+ mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+ LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
+ }
+
+ @Test
+ public void testSupportsInputWakeDelegatse_publishesLocalService() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNotNull();
+ }
+
+ @Test
+ public void testDoesNotSupportInputWakeDelegatse_doesNotPublishLocalService() {
+ mSetFlagsRule.disableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNull();
+ }
+
+ @Test
+ public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+ setTheaterModeEnabled(false);
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ setDelegatedMotionWakeUpResult(true);
+
+ // Verify the policy wake up call succeeds because of the call on the delegate, and not
+ // because of a PowerManager wake up.
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
+ verifyNoPowerManagerWakeUp();
+
+ setDelegatedMotionWakeUpResult(false);
+
+ // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
+ // delegate would not handle the wake up request.
+ assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
+ verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
+ }
+
+ @Test
+ public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+ setTheaterModeEnabled(false);
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ setDelegatedKeyWakeUpResult(true);
+
+ // Verify the policy wake up call succeeds because of the call on the delegate, and not
+ // because of a PowerManager wake up.
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
+ verifyNoPowerManagerWakeUp();
+
+ setDelegatedKeyWakeUpResult(false);
+
+ // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
+ // delegate would not handle the wake up request.
+ assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
+ verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
+ }
+
+ @Test
+ public void testDelegatedKeyWakeIsSubjectToPolicyChecks() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ setDelegatedKeyWakeUpResult(true);
+ setTheaterModeEnabled(true);
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+ setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ // Check that the wake up does not happen because the theater mode policy check fails.
+ assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testDelegatedMotionWakeIsSubjectToPolicyChecks() {
+ mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+ setDelegatedMotionWakeUpResult(true);
+ setTheaterModeEnabled(true);
+ setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ LocalServices.getService(WindowWakeUpPolicyInternal.class)
+ .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+ // Check that the wake up does not happen because the theater mode policy check fails.
+ assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testWakeUpFromMotion() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ config_allowTheaterModeWakeFromMotion,
+ WAKE_REASON_WAKE_MOTION,
+ "android.policy:MOTION");
+ }
+
+ @Test
+ public void testWakeUpFromKey_nonPowerKey() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ config_allowTheaterModeWakeFromKey,
+ WAKE_REASON_WAKE_KEY,
+ "android.policy:KEY");
+ }
+
+ @Test
+ public void testWakeUpFromKey_powerKey() {
+ // Disable the resource affecting all wake keys because it affects power key as well.
+ // That way, power key wake during theater mode will solely be controlled by
+ // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+ // Test with power key
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ config_allowTheaterModeWakeFromPowerKey,
+ WAKE_REASON_POWER_BUTTON,
+ "android.policy:POWER");
+
+ // Test that power key wake ups happen during theater mode as long as wake-keys are allowed
+ // even if the power-key specific theater mode config is disabled.
+ setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ config_allowTheaterModeWakeFromKey,
+ WAKE_REASON_POWER_BUTTON,
+ "android.policy:POWER");
+ }
+
+ @Test
+ public void testWakeUpFromLid() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromLid(),
+ config_allowTheaterModeWakeFromLidSwitch,
+ WAKE_REASON_LID,
+ "android.policy:LID");
+ }
+
+ @Test
+ public void testWakeUpFromWakeGesture() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromWakeGesture(),
+ config_allowTheaterModeWakeFromGesture,
+ WAKE_REASON_GESTURE,
+ "android.policy:GESTURE");
+ }
+
+ @Test
+ public void testwakeUpFromCameraCover() {
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
+ config_allowTheaterModeWakeFromCameraLens,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_COVER");
+ }
+
+ @Test
+ public void testWakeUpFromPowerKeyCameraGesture() {
+ // Disable the resource affecting all wake keys because it affects power key as well.
+ // That way, power key wake during theater mode will solely be controlled by
+ // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+ setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+ runPowerManagerUpChecks(
+ () -> mPolicy.wakeUpFromPowerKeyCameraGesture(),
+ config_allowTheaterModeWakeFromPowerKey,
+ WAKE_REASON_CAMERA_LAUNCH,
+ "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
+ }
+
+ private void runPowerManagerUpChecks(
+ BooleanSupplier wakeUpCall,
+ int theatherModeWakeResId,
+ int expectedWakeReason,
+ String expectedWakeDetails) {
+ // Test under theater mode enabled.
+ setTheaterModeEnabled(true);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, true);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(200);
+ assertWithMessage("Wake should happen in theater mode when config allows it.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(200L, expectedWakeReason, expectedWakeDetails);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(250);
+ assertWithMessage("Wake should not happen in theater mode when config disallows it.")
+ .that(wakeUpCall.getAsBoolean()).isFalse();
+ verifyNoPowerManagerWakeUp();
+
+ // Cases when theater mode is disabled.
+ setTheaterModeEnabled(false);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, true);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(300);
+ assertWithMessage("Wake should happen when not in theater mode.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(300L, expectedWakeReason, expectedWakeDetails);
+
+ Mockito.reset(mPowerManager);
+ setBooleanRes(theatherModeWakeResId, false);
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+ setUptimeMillis(350);
+ assertWithMessage("Wake should happen when not in theater mode.")
+ .that(wakeUpCall.getAsBoolean()).isTrue();
+ verify(mPowerManager).wakeUp(350L, expectedWakeReason, expectedWakeDetails);
+ }
+
+ private void verifyNoPowerManagerWakeUp() {
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ private void setBooleanRes(int resId, boolean val) {
+ when(mResourcesSpy.getBoolean(resId)).thenReturn(val);
+ }
+
+ private void setUptimeMillis(long uptimeMillis) {
+ when(mClock.uptimeMillis()).thenReturn(uptimeMillis);
+ }
+
+ private void setTheaterModeEnabled(boolean enabled) {
+ Settings.Global.putInt(
+ mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, enabled ? 1 : 0);
+ }
+
+ private void setDelegatedMotionWakeUpResult(boolean result) {
+ when(mInputWakeUpDelegate.wakeUpFromMotion(anyLong(), anyInt(), anyBoolean()))
+ .thenReturn(result);
+ }
+
+ private void setDelegatedKeyWakeUpResult(boolean result) {
+ when(mInputWakeUpDelegate.wakeUpFromKey(anyLong(), anyInt(), anyBoolean()))
+ .thenReturn(result);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f049b3398f42..f5282cb492f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -143,7 +143,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
-import android.util.MergedConfiguration;
import android.util.MutableBoolean;
import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
@@ -395,8 +394,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -420,8 +418,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -447,8 +444,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~CONFIG_ORIENTATION;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -468,8 +464,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
activity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -571,8 +566,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.build();
activity.setState(RESUMED, "Testing");
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
clearInvocations(mClientLifecycleManager);
@@ -799,8 +793,7 @@ public class ActivityRecordTests extends WindowTestsBase {
doReturn(false).when(stack).isTranslucent(any());
assertTrue(task.shouldBeVisible(null /* starting */));
- activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
- activity.getConfiguration()));
+ activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
final Configuration newConfig = new Configuration(activity.getConfiguration());
final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp
@@ -846,7 +839,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testTakeOptions() {
+ public void testTakeSceneTransitionInfo() {
final ActivityRecord activity = createActivityWithTask();
ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
new RemoteAnimationAdapter(new Stub() {
@@ -864,7 +857,9 @@ public class ActivityRecordTests extends WindowTestsBase {
}
}, 0, 0));
activity.updateOptionsLocked(opts);
- assertNotNull(activity.takeOptions());
+ // Ensure the SceneTransitionInfo is null (since the ActivityOptions is for remote
+ // animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
+ assertNull(activity.takeSceneTransitionInfo());
assertNull(activity.getOptions());
final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index ac18f802d1c6..d83824a39730 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -464,9 +464,10 @@ public class BackNavigationControllerTests extends WindowTestsBase {
// Simulate ActivityOptions#makeSceneTransitionAnimation
final Bundle myBundle = new Bundle();
myBundle.putInt(ActivityOptions.KEY_ANIM_TYPE, ANIM_SCENE_TRANSITION);
- myBundle.putParcelable("android:activity.transitionCompleteListener",
- mock(android.os.ResultReceiver.class));
final ActivityOptions options = new ActivityOptions(myBundle);
+ final ActivityOptions.SceneTransitionInfo info = new ActivityOptions.SceneTransitionInfo();
+ info.setResultReceiver(mock(android.os.ResultReceiver.class));
+ options.setSceneTransitionInfo(info);
final ActivityRecord testActivity = new ActivityBuilder(mAtm)
.setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index dfa595c23e44..99d354aa0505 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -55,6 +55,11 @@ import org.mockito.ArgumentCaptor;
@RunWith(WindowTestRunner.class)
public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
+ // The fields to override the current DisplayInfo.
+ private String mUniqueId;
+ private int mColorMode;
+ private int mLogicalDensityDpi;
+
@Override
protected void onBeforeSystemServicesCreated() {
// Set other flags to their default values
@@ -73,7 +78,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -82,11 +87,21 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
verify(onUpdated).run();
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
- givenDisplayInfo(/* uniqueId= */ "new");
+ mUniqueId = "new";
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
verify(onUpdated).run();
+ verify(mDisplayContent.mTransitionController).requestStartTransition(
+ any(), any(), any(), any());
assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+ clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+ mLogicalDensityDpi += 100;
+ mDisplayContent.requestDisplayUpdate(onUpdated);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+ verify(onUpdated).run();
+ verify(mDisplayContent.mTransitionController).requestStartTransition(
+ any(), any(), any(), any());
}
@Test
@@ -94,7 +109,8 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
performInitialDisplayUpdate();
// Update only color mode (non-deferrable field) and keep the same unique id
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ mUniqueId = "initial_unique_id";
+ mColorMode = 123;
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -107,7 +123,8 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
performInitialDisplayUpdate();
// Update only color mode (non-deferrable field) and keep the same unique id
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+ mUniqueId = "initial_unique_id";
+ mColorMode = 123;
mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -116,7 +133,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
// Update unique id (deferrable field), keep the same color mode,
// this update should be deferred
- givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+ mUniqueId = "new_unique_id";
mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -126,7 +143,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
// Update color mode again and keep the same unique id, color mode update
// should not be deferred, unique id update is still deferred as transition
// has not started collecting yet
- givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+ mColorMode = 456;
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -146,14 +163,14 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
verify(onUpdated).run();
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
- givenDisplayInfo(/* uniqueId= */ "new");
+ mUniqueId = "new";
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection(); // do not continue by not starting the collection
@@ -164,7 +181,7 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
performInitialDisplayUpdate();
- givenDisplayInfo(/* uniqueId= */ "old");
+ mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
captureStartTransitionCollection().getValue()
@@ -173,10 +190,10 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
clearInvocations(mDisplayContent.mTransitionController, onUpdated);
// Perform two display updates while WM is 'busy'
- givenDisplayInfo(/* uniqueId= */ "new1");
+ mUniqueId = "new1";
Runnable onUpdated1 = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated1);
- givenDisplayInfo(/* uniqueId= */ "new2");
+ mUniqueId = "new2";
Runnable onUpdated2 = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated2);
@@ -215,22 +232,19 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
return callbackCaptor;
}
- private void givenDisplayInfo(String uniqueId) {
- givenDisplayInfo(uniqueId, /* colorMode= */ 0);
- }
+ private void performInitialDisplayUpdate() {
+ mUniqueId = "initial_unique_id";
+ mColorMode = 0;
+ mLogicalDensityDpi = 400;
- private void givenDisplayInfo(String uniqueId, int colorMode) {
spyOn(mDisplayContent.mDisplay);
doAnswer(invocation -> {
DisplayInfo info = invocation.getArgument(0);
- info.uniqueId = uniqueId;
- info.colorMode = colorMode;
+ info.uniqueId = mUniqueId;
+ info.colorMode = mColorMode;
+ info.logicalDensityDpi = mLogicalDensityDpi;
return null;
}).when(mDisplayContent.mDisplay).getDisplayInfo(any());
- }
-
- private void performInitialDisplayUpdate() {
- givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 28fecd665662..60130635108c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -76,7 +76,6 @@ import android.graphics.Rect;
import android.os.PowerManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
import android.util.Pair;
import androidx.test.filters.MediumTest;
@@ -545,8 +544,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation);
// Assume the activity was shown in different orientation. For example, the top activity is
// landscape and the portrait lockscreen is shown.
- activity.setLastReportedConfiguration(
- new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
+ activity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig);
activity.setState(STOPPED, "sleep");
display.setIsSleeping(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
new file mode 100644
index 000000000000..71dbc57e5065
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:SensitiveContentPackagesTest
+ */
+@SmallTest
+@Presubmit
+public class SensitiveContentPackagesTest {
+ private static final String APP_PKG_1 = "com.android.server.wm.one";
+ private static final String APP_PKG_2 = "com.android.server.wm.two";
+ private static final String APP_PKG_3 = "com.android.server.wm.three";
+
+ private static final int APP_UID_1 = 5;
+ private static final int APP_UID_2 = 6;
+ private static final int APP_UID_3 = 7;
+
+
+ private final SensitiveContentPackages mSensitiveContentPackages =
+ new SensitiveContentPackages();
+
+ @After
+ public void tearDown() {
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp() {
+ Set<PackageInfo> blockedApps =
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2));
+
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp_empty() {
+ Set<PackageInfo> blockedApps =
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2));
+
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+ mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 810cbe8f8080..6c5f9752b6fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -134,6 +135,33 @@ public class SyncEngineTests extends WindowTestsBase {
assertFalse(r.isSyncFinished(r.getSyncGroup()));
r.finishRelaunching();
assertTrue(r.isSyncFinished(r.getSyncGroup()));
+ assertEquals(SYNC_STATE_READY, r.mSyncState);
+
+ // If the container has finished the sync, isSyncFinished should not change the sync state.
+ final BLASTSyncEngine.SyncGroup syncGroup = r.getSyncGroup();
+ r.finishSync(mTransaction, syncGroup, false /* cancel */);
+ assertEquals(SYNC_STATE_NONE, r.mSyncState);
+ assertTrue(r.isSyncFinished(syncGroup));
+ assertEquals(SYNC_STATE_NONE, r.mSyncState);
+ }
+
+ @Test
+ public void testFinishSyncByStartingWindow() {
+ final ActivityRecord taskRoot = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = taskRoot.getTask();
+ final ActivityRecord translucentTop = new ActivityBuilder(mAtm).setTask(task)
+ .setActivityTheme(android.R.style.Theme_Translucent).build();
+ createWindow(null, TYPE_BASE_APPLICATION, taskRoot, "win");
+ final WindowState startingWindow = createWindow(null, TYPE_APPLICATION_STARTING,
+ translucentTop, "starting");
+ startingWindow.mStartingData = new SnapshotStartingData(mWm, null, 0);
+ task.mSharedStartingData = startingWindow.mStartingData;
+ task.prepareSync();
+
+ final BLASTSyncEngine.SyncGroup group = mock(BLASTSyncEngine.SyncGroup.class);
+ assertFalse(task.isSyncFinished(group));
+ startingWindow.onSyncFinishedDrawing();
+ assertTrue(task.isSyncFinished(group));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 45e1e9579f3b..b36080023ef2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -43,6 +43,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.google.common.truth.Truth.assertThat;
@@ -1620,6 +1621,29 @@ public class TaskTests extends WindowTestsBase {
}
@Test
+ public void testBoostDimmingTaskFragmentOnTask() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment primary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment secondary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+
+ primary.mVisibleRequested = true;
+ secondary.mVisibleRequested = true;
+ primary.setAdjacentTaskFragment(secondary);
+ secondary.setAdjacentTaskFragment(primary);
+ primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+ doReturn(true).when(primary).shouldBoostDimmer();
+ task.assignChildLayers(t);
+
+ // The layers are initially assigned via the hierarchy, but the primary will be boosted and
+ // assigned again to above of the secondary.
+ verify(primary).assignLayer(t, 0);
+ verify(secondary).assignLayer(t, 1);
+ verify(primary).assignLayer(t, 2);
+ }
+
+ @Test
public void testMoveOrCreateDecorSurface() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 21fee7286a7b..a1cc8d5d9188 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -80,6 +80,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
@@ -102,16 +103,19 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerService.WindowContainerInfo;
import com.google.common.truth.Expect;
+import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.Collections;
/**
* Build/Install/Run:
@@ -133,6 +137,11 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@Rule
public Expect mExpect = Expect.create();
+ @After
+ public void tearDown() {
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
@Test
public void testIsRequestedOrientationMapped() {
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -815,6 +824,42 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ public void setShouldBlockScreenCaptureForApp() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+ verify(mWm).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+ wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ verify(mWm, times(2)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
public void testisLetterboxBackgroundMultiColored() {
assertThat(setupLetterboxConfigurationWithBackgroundType(
LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f24baba9ca0c..fb4edfacb8e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -55,6 +55,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
@@ -90,6 +91,7 @@ import android.os.IBinder;
import android.os.InputConfig;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.Gravity;
@@ -109,7 +111,9 @@ import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -130,6 +134,11 @@ import java.util.List;
@RunWith(WindowTestRunner.class)
public class WindowStateTests extends WindowTestsBase {
+ @After
+ public void tearDown() {
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ }
+
@Test
public void testIsParentWindowHidden() {
final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
@@ -1373,6 +1382,28 @@ public class WindowStateTests extends WindowTestsBase {
assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ final WindowState window1 = createWindow(null, TYPE_APPLICATION, "window1", ownerId1);
+ final WindowState window2 = createWindow(null, TYPE_APPLICATION, "window2", ownerId2);
+
+ // Setting packagename for targeted feature
+ window1.mAttrs.packageName = testPackage;
+ window2.mAttrs.packageName = testPackage;
+
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+ mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+ assertTrue(window1.isSecureLocked());
+ assertFalse(window2.isSecureLocked());
+ }
+
private static class TestImeTargetChangeListener implements ImeTargetChangeListener {
private IBinder mImeTargetToken;
private boolean mIsRemoved;
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index 61d7ead67a21..c902016d2cf0 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -169,6 +169,7 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
public static final int SECURITY_ALGORITHM_NEA1 = 56;
public static final int SECURITY_ALGORITHM_NEA2 = 57;
public static final int SECURITY_ALGORITHM_NEA3 = 58;
+ public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
public static final int SECURITY_ALGORITHM_AES_GCM = 69;
public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
@@ -176,9 +177,8 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
- public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
- public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
- public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+ public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+ public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
@@ -199,15 +199,15 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
- SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+ SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
- SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
- SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
- SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
- SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
- SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
- SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+ SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
+ SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
+ SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
+ SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
+ SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
+ SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
public @interface SecurityAlgorithm {
}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e981e1f92071..69594f27e65c 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -183,6 +183,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and all the profiles.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestAllProfiles(String cardId, @CallbackExecutor Executor executor,
ResultCallback<EuiccProfileInfo[]> callback) {
@@ -212,6 +215,9 @@ public class EuiccCardManager {
* @param iccid The iccid of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and profile.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
ResultCallback<EuiccProfileInfo> callback) {
@@ -244,6 +250,9 @@ public class EuiccCardManager {
* ICCID is known, an APDU will be sent through to read the enabled profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the profile.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEnabledProfileForPort(@NonNull String cardId, int portIndex,
@NonNull @CallbackExecutor Executor executor,
@@ -276,6 +285,9 @@ public class EuiccCardManager {
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void disableProfile(String cardId, String iccid, boolean refresh,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -307,6 +319,9 @@ public class EuiccCardManager {
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor,
* ResultCallback)}
*/
@@ -344,6 +359,9 @@ public class EuiccCardManager {
* @param refresh Whether sending the REFRESH command to modem.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
boolean refresh, @NonNull @CallbackExecutor Executor executor,
@@ -375,6 +393,9 @@ public class EuiccCardManager {
* @param nickname The nickname of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void setNickname(String cardId, String iccid, String nickname,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -404,6 +425,9 @@ public class EuiccCardManager {
* @param iccid The iccid of the profile.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void deleteProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
ResultCallback<Void> callback) {
@@ -434,6 +458,9 @@ public class EuiccCardManager {
* EuiccCard for details.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void resetMemory(String cardId, @ResetOption int options,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -462,6 +489,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the default SM-DP+ address.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestDefaultSmdpAddress(String cardId, @CallbackExecutor Executor executor,
ResultCallback<String> callback) {
@@ -490,6 +520,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code and the SM-DS address.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestSmdsAddress(String cardId, @CallbackExecutor Executor executor,
ResultCallback<String> callback) {
@@ -519,6 +552,9 @@ public class EuiccCardManager {
* @param defaultSmdpAddress The default SM-DP+ address to set.
* @param executor The executor through which the callback should be invoked.
* @param callback The callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void setDefaultSmdpAddress(String cardId, String defaultSmdpAddress,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -548,6 +584,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the rule authorisation table.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestRulesAuthTable(String cardId, @CallbackExecutor Executor executor,
ResultCallback<EuiccRulesAuthTable> callback) {
@@ -576,6 +615,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the challenge.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccChallenge(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -604,6 +646,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the info1.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccInfo1(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -632,6 +677,9 @@ public class EuiccCardManager {
* @param cardId The Id of the eUICC.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the info2.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void requestEuiccInfo2(String cardId, @CallbackExecutor Executor executor,
ResultCallback<byte[]> callback) {
@@ -671,6 +719,9 @@ public class EuiccCardManager {
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void authenticateServer(String cardId, String matchingId, byte[] serverSigned1,
byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed, byte[] serverCertificate,
@@ -716,6 +767,9 @@ public class EuiccCardManager {
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void prepareDownload(String cardId, @Nullable byte[] hashCc, byte[] smdpSigned2,
byte[] smdpSignature2, byte[] smdpCertificate, @CallbackExecutor Executor executor,
@@ -753,6 +807,9 @@ public class EuiccCardManager {
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and a byte array which represents a
* {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void loadBoundProfilePackage(String cardId, byte[] boundProfilePackage,
@CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -787,6 +844,9 @@ public class EuiccCardManager {
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and an byte[] which represents a
* {@code CancelSessionResponse} defined in GSMA RSP v2.0+.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void cancelSession(String cardId, byte[] transactionId, @CancelReason int reason,
@CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -820,6 +880,9 @@ public class EuiccCardManager {
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the list of notifications.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void listNotifications(String cardId, @EuiccNotification.Event int events,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -850,6 +913,9 @@ public class EuiccCardManager {
* @param events bits of the event types ({@link EuiccNotification.Event}) to list.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the list of notifications.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void retrieveNotificationList(String cardId, @EuiccNotification.Event int events,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -880,6 +946,9 @@ public class EuiccCardManager {
* @param seqNumber the sequence number of the notification.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code and the notification.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void retrieveNotification(String cardId, int seqNumber,
@CallbackExecutor Executor executor, ResultCallback<EuiccNotification> callback) {
@@ -910,6 +979,9 @@ public class EuiccCardManager {
* @param seqNumber the sequence number of the notification.
* @param executor The executor through which the callback should be invoked.
* @param callback the callback to get the result code.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public void removeNotificationFromList(String cardId, int seqNumber,
@CallbackExecutor Executor executor, ResultCallback<Void> callback) {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 86fbb04d31b6..09d21083afb1 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -927,6 +927,9 @@ public class EuiccManager {
* subscription APIs.
*
* @return true if embedded subscriptions are currently enabled.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public boolean isEnabled() {
// In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
@@ -942,6 +945,9 @@ public class EuiccManager {
* access to the EID of another eUICC.
*
* @return the EID. May be null if the eUICC is not ready.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
public String getEid() {
@@ -963,6 +969,8 @@ public class EuiccManager {
* @return the status of eUICC OTA. If the eUICC is not ready,
* {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1014,6 +1022,9 @@ public class EuiccManager {
* @param subscription the subscription to download.
* @param switchAfterDownload if true, the profile will be activated upon successful download.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void downloadSubscription(DownloadableSubscription subscription,
@@ -1075,6 +1086,9 @@ public class EuiccManager {
* @param resolutionExtras Resolution-specific extras depending on the result of the resolution.
* For example, this may indicate whether the user has consented or may include the input
* they provided.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1111,6 +1125,9 @@ public class EuiccManager {
*
* @param subscription the subscription which needs metadata filled in
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1142,6 +1159,9 @@ public class EuiccManager {
* internal system use only.
*
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1163,6 +1183,9 @@ public class EuiccManager {
* Returns information about the eUICC chip/device.
*
* @return the {@link EuiccInfo}. May be null if the eUICC is not ready.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@Nullable
public EuiccInfo getEuiccInfo() {
@@ -1188,6 +1211,9 @@ public class EuiccManager {
*
* @param subscriptionId the ID of the subscription to delete.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1251,6 +1277,9 @@ public class EuiccManager {
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or the
* calling app must be authorized to manage the active subscription on the target eUICC.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1312,6 +1341,9 @@ public class EuiccManager {
* {@link SubscriptionInfo#getPortIndex()}.
* @param portIndex the index of the port to target for the enabled subscription
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void switchToSubscription(int subscriptionId, int portIndex,
@@ -1349,6 +1381,9 @@ public class EuiccManager {
* @param subscriptionId the ID of the subscription to update.
* @param nickname the new nickname to apply.
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
public void updateSubscriptionNickname(
@@ -1376,6 +1411,8 @@ public class EuiccManager {
* @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
* and use {@link #eraseSubscriptions(int, PendingIntent)} instead
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1402,6 +1439,8 @@ public class EuiccManager {
* @param options flag indicating specific set of subscriptions to erase
* @param callbackIntent a PendingIntent to launch when the operation completes.
*
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1459,6 +1498,9 @@ public class EuiccManager {
* determine whether a country is supported please check {@link #isSupportedCountry}.
*
* @param supportedCountries is a list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1487,6 +1529,9 @@ public class EuiccManager {
* determine whether a country is supported please check {@link #isSupportedCountry}.
*
* @param unsupportedCountries is a list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1512,6 +1557,9 @@ public class EuiccManager {
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @return list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1535,6 +1583,9 @@ public class EuiccManager {
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @return list of strings contains country ISO codes in uppercase.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1566,6 +1617,9 @@ public class EuiccManager {
* @param countryIso should be the ISO-3166 country code is provided in uppercase 2 character
* format.
* @return whether the given country supports eUICC or not.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
* @hide
*/
@SystemApi
@@ -1630,6 +1684,9 @@ public class EuiccManager {
*
* @param portIndex is an enumeration of the ports available on the UICC.
* @return {@code true} if port is available
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
public boolean isSimPortAvailable(int portIndex) {
try {
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 71bb329a7281..551057fc43d1 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -779,6 +779,8 @@ public class ImsMmTelManager implements RegistrationManager {
* @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for advanced calling is enabled, false otherwise.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -827,6 +829,8 @@ public class ImsMmTelManager implements RegistrationManager {
* @see #isAdvancedCallingSettingEnabled()
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -865,6 +869,8 @@ public class ImsMmTelManager implements RegistrationManager {
* @param capability The IMS MmTel capability to query.
* @return {@code true} if the MmTel IMS capability is capable for this subscription, false
* otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -893,6 +899,8 @@ public class ImsMmTelManager implements RegistrationManager {
* @param capability The IMS MmTel capability to query.
* @return {@code true} if the MmTel IMS capability is available for this subscription, false
* otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -986,6 +994,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user’s “Video Calling” setting is currently enabled.
*/
@RequiresPermission(anyOf = {
@@ -1017,6 +1027,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #isVtSettingEnabled()
* @hide
*/
@@ -1060,6 +1072,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@RequiresPermission(anyOf = {
@@ -1090,6 +1104,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
* @see #isVoWiFiSettingEnabled()
* @hide
@@ -1148,6 +1164,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws ImsException if the IMS service associated with this subscription is not available or
* the IMS service is not available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -1192,6 +1210,8 @@ public class ImsMmTelManager implements RegistrationManager {
* </ul>
* @throws ImsException if the IMS service associated with this subscription is not available or
* the IMS service is not available.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
* false otherwise
* @see #isCrossSimCallingEnabled()
@@ -1233,6 +1253,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return true if the user's setting for Voice over WiFi while roaming is enabled, false
* if disabled.
*/
@@ -1267,6 +1289,8 @@ public class ImsMmTelManager implements RegistrationManager {
* false otherwise.
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #isVoWiFiRoamingSettingEnabled()
* @hide
*/
@@ -1304,6 +1328,8 @@ public class ImsMmTelManager implements RegistrationManager {
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #setVoWiFiSettingEnabled(boolean)
* @hide
*/
@@ -1347,6 +1373,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @return The Voice over WiFi Mode preference set by the user, which can be one of the
* following:
* - {@link #WIFI_MODE_WIFI_ONLY}
@@ -1386,6 +1414,8 @@ public class ImsMmTelManager implements RegistrationManager {
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #getVoWiFiModeSetting()
* @hide
*/
@@ -1422,6 +1452,8 @@ public class ImsMmTelManager implements RegistrationManager {
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #setVoWiFiRoamingSettingEnabled(boolean)
* @hide
*/
@@ -1458,6 +1490,8 @@ public class ImsMmTelManager implements RegistrationManager {
* - {@link #WIFI_MODE_WIFI_PREFERRED}
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see #getVoWiFiRoamingModeSetting()
* @hide
*/
@@ -1492,6 +1526,8 @@ public class ImsMmTelManager implements RegistrationManager {
* settings.
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @param isEnabled if true RTT should be enabled during calls made on this subscription.
* @hide
*/
@@ -1535,6 +1571,8 @@ public class ImsMmTelManager implements RegistrationManager {
*
* @throws IllegalArgumentException if the subscription associated with this operation is not
* active (SIM is not inserted, ESIM inactive) or invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
*/
@SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 2b49bcd4e928..62d426383e57 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -250,6 +250,8 @@ public class ImsRcsManager {
* the {@code ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void registerImsRegistrationCallback(
@@ -294,6 +296,8 @@ public class ImsRcsManager {
* @param c The {@link RegistrationManager.RegistrationCallback} to be removed.
* @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
* @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void unregisterImsRegistrationCallback(
@@ -329,6 +333,8 @@ public class ImsRcsManager {
* following: {@link RegistrationManager#REGISTRATION_STATE_NOT_REGISTERED},
* {@link RegistrationManager#REGISTRATION_STATE_REGISTERING}, or
* {@link RegistrationManager#REGISTRATION_STATE_REGISTERED}.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
@@ -378,6 +384,8 @@ public class ImsRcsManager {
* {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
* {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
* {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@@ -435,6 +443,8 @@ public class ImsRcsManager {
* {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -479,6 +489,8 @@ public class ImsRcsManager {
* @see #addOnAvailabilityChangedListener(Executor, OnAvailabilityChangedListener)
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -525,6 +537,8 @@ public class ImsRcsManager {
* @see android.telephony.CarrierConfigManager.Ims#KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -563,6 +577,8 @@ public class ImsRcsManager {
* @see #isCapable(int, int)
* @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 1c5d1e940030..62b8420fc66e 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1300,8 +1300,10 @@ public class ProvisioningManager {
* @param executor The executor that the callback methods will be called on.
* @param callback The callback instance being registered.
* @throws ImsException if the subscription associated with this callback is
- * valid, but the {@link ImsService the service crashed, for example. See
+ * valid, but the service crashed, for example. See
* {@link ImsException#getCode()} for a more detailed reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public void registerFeatureProvisioningChangedCallback(
@@ -1327,6 +1329,8 @@ public class ProvisioningManager {
*
* @param callback The existing {@link FeatureProvisioningCallback} to be removed.
* @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback)
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
public void unregisterFeatureProvisioningChangedCallback(
@NonNull FeatureProvisioningCallback callback) {
@@ -1347,6 +1351,8 @@ public class ProvisioningManager {
* @return an integer value for the provided key, or
* {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1369,6 +1375,8 @@ public class ProvisioningManager {
* @return a String value for the provided key, {@code null} if the key doesn't exist, or
* {@link StringResultError} if there was an error getting the value for the provided key.
* @throws IllegalArgumentException if the key provided was invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1392,6 +1400,8 @@ public class ProvisioningManager {
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @param value a integer value for the provided key.
* @return the result of setting the configuration value.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*
* Note: For compatibility purposes, the integer values [0 - 99] used in
@@ -1420,6 +1430,8 @@ public class ProvisioningManager {
* should be appropriately namespaced to avoid collision.
* @param value a String value for the provided key.
* @return the result of setting the configuration value.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -1451,6 +1463,9 @@ public class ProvisioningManager {
*
* @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
* @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1485,6 +1500,9 @@ public class ProvisioningManager {
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1509,6 +1527,9 @@ public class ProvisioningManager {
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+ *
* @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead,
* as this only retrieves provisioning information for
* {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1546,6 +1567,9 @@ public class ProvisioningManager {
* @return true if the device is provisioned for the capability or does not require
* provisioning, false if the capability does require provisioning and has not been
* provisioned yet.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1577,6 +1601,9 @@ public class ProvisioningManager {
* @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
* @param isProvisioned true if the device is provisioned for the RCS capability specified,
* false otherwise.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+ *
* @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead,
* as this method only sets provisioning information for
* {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1615,6 +1642,9 @@ public class ProvisioningManager {
* @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
* @param isProvisioned true if the device is provisioned for the RCS capability specified,
* false otherwise.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@WorkerThread
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1644,6 +1674,9 @@ public class ProvisioningManager {
* @return true if provisioning is required for the MMTEL capability and IMS
* registration technology specified, false if it is not required or if the device does not
* support IMS.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isProvisioningRequiredForCapability(
@@ -1672,6 +1705,9 @@ public class ProvisioningManager {
* @return true if provisioning is required for the RCS capability and IMS
* registration technology specified, false if it is not required or if the device does not
* support IMS.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
public boolean isRcsProvisioningRequiredForCapability(
@@ -1700,10 +1736,14 @@ public class ProvisioningManager {
* @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
if (config == null) {
throw new IllegalArgumentException("Must include a non-null config XML file.");
@@ -1714,7 +1754,6 @@ public class ProvisioningManager {
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
-
}
/**
@@ -1787,10 +1826,14 @@ public class ProvisioningManager {
* When the IMS/RCS service receives the RCS client configuration, it will detect
* the change in the configuration, and trigger the auto-configuration as needed.
* @param rcc RCS client configuration {@link RcsClientConfiguration}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void setRcsClientConfiguration(
@NonNull RcsClientConfiguration rcc) throws ImsException {
try {
@@ -1826,6 +1869,7 @@ public class ProvisioningManager {
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
try {
return getITelephony().isRcsVolteSingleRegistrationCapable(mSubId);
@@ -1870,12 +1914,15 @@ public class ProvisioningManager {
* params (See {@link #setRcsClientConfiguration}) and re register the
* callback.
* See {@link ImsException#getCode()} for a more detailed reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void registerRcsProvisioningCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull RcsProvisioningCallback callback) throws ImsException {
@@ -1908,12 +1955,15 @@ public class ProvisioningManager {
* @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)
* @throws IllegalArgumentException if the subscription associated with
* this callback is invalid.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void unregisterRcsProvisioningCallback(
@NonNull RcsProvisioningCallback callback) {
try {
@@ -1935,10 +1985,14 @@ public class ProvisioningManager {
* {@link RcsProvisioningCallback#onConfigurationReset}, then
* {@link RcsProvisioningCallback#onConfigurationChanged} when the new
* RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived}
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
public void triggerRcsReconfiguration() {
try {
getITelephony().triggerRcsReconfiguration(mSubId);
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 3bb9be0252cd..8925a9e82942 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,9 +21,11 @@ import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -49,6 +51,7 @@ import java.util.concurrent.Executor;
*
* @see ImsRcsManager#getUceAdapter() for information on creating an instance of this class.
*/
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
public class RcsUceAdapter {
private static final String TAG = "RcsUceAdapter";
@@ -585,6 +588,8 @@ public class RcsUceAdapter {
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -682,6 +687,8 @@ public class RcsUceAdapter {
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -759,6 +766,8 @@ public class RcsUceAdapter {
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -800,6 +809,8 @@ public class RcsUceAdapter {
* the {@link ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -845,6 +856,8 @@ public class RcsUceAdapter {
* the {@link ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
* reason.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
@@ -901,6 +914,8 @@ public class RcsUceAdapter {
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
*/
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
public boolean isUceSettingEnabled() throws ImsException {
@@ -954,6 +969,8 @@ public class RcsUceAdapter {
* {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
* available. This can happen if the ImsService has crashed, for example, or if the subscription
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS}.
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 25ebdd0b8b40..abf2105327a3 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -525,6 +525,8 @@ public class SipDelegateManager {
* @param callback The callback instance being registered.
* @throws ImsException in the case that the callback can not be registered.
* See {@link ImsException#getCode} for more information on when this is called.
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerSipDialogStateCallback(@NonNull Executor executor,
@@ -557,6 +559,9 @@ public class SipDelegateManager {
* {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
*
* @param callback The callback instance to be unregistered.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterSipDialogStateCallback(@NonNull SipDialogStateCallback callback)
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 84777c9441a1..9b5ee0cd82f3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3055,6 +3055,29 @@ interface ITelephony {
boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds);
/**
+ * This API should be used by only CTS tests to forcefully set the country codes.
+ *
+ * @param reset {@code true} mean the overridden country codes should not be used, {@code false}
+ * otherwise.
+ * @return {@code true} if the country code is set successfully, {@code false} otherwise.
+ */
+ boolean setCountryCodes(in boolean reset, in List<String> currentNetworkCountryCodes,
+ in Map cachedNetworkCountryCodes, in String locationCountryCode,
+ in long locationCountryCodeTimestampNanos);
+
+ /**
+ * This API should be used by only CTS tests to override the overlay configs of satellite
+ * access controller.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @return {@code true} if the overlay configs are set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteAccessControlOverlayConfigs(in boolean reset, in boolean isAllowed,
+ in String s2CellFile, in long locationFreshDurationNanos,
+ in List<String> satelliteCountryCodes);
+
+ /**
* Test method to confirm the file contents are not altered.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index ba9e4a831789..f82d9ca13938 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -130,14 +130,13 @@ public class UpdatableSystemFontTest {
private static final Pattern PATTERN_SYSTEM_FONT_FILES =
Pattern.compile("^/(system|product)/fonts/");
- private String mKeyId;
private FontManager mFontManager;
private UiDevice mUiDevice;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- mKeyId = insertCert(CERT_PATH);
+ insertCert(CERT_PATH);
mFontManager = context.getSystemService(FontManager.class);
expectCommandToSucceed("cmd font clear");
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -147,9 +146,6 @@ public class UpdatableSystemFontTest {
public void tearDown() throws Exception {
// Ignore errors because this may fail if updatable system font is not enabled.
runShellCommand("cmd font clear", null);
- if (mKeyId != null) {
- expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
- }
}
@Test
@@ -369,20 +365,11 @@ public class UpdatableSystemFontTest {
assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse();
}
- private static String insertCert(String certPath) throws Exception {
- Pair<String, String> result;
- try (InputStream is = new FileInputStream(certPath)) {
- result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
- }
+ private static void insertCert(String certPath) throws Exception {
// /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
final String copiedCert = "/data/fonts/debug_cert.der";
runShellCommand("cp " + certPath + " " + copiedCert, null);
runShellCommand("cmd font install-debug-cert " + copiedCert, null);
- // Assert that there are no errors.
- assertThat(result.second).isEmpty();
- String keyId = result.first.trim();
- assertThat(keyId).matches("^\\d+$");
- return keyId;
}
private int updateFontFile(String fontPath, String signaturePath) throws IOException {