summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/aconfig/Android.bp3
-rw-r--r--apex/jobscheduler/service/aconfig/alarm.aconfig1
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig1
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java91
-rw-r--r--api/Android.bp12
-rw-r--r--api/StubLibraries.bp123
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/AppOpsManager.java22
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java35
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig7
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java11
-rw-r--r--core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java30
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java17
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java29
-rw-r--r--core/java/android/hardware/devicestate/DeviceState.java27
-rw-r--r--core/java/android/hardware/devicestate/OWNERS1
-rw-r--r--core/java/android/permission/TEST_MAPPING24
-rw-r--r--core/java/android/service/dreams/DreamService.java53
-rw-r--r--core/java/android/service/dreams/flags.aconfig11
-rw-r--r--core/java/android/view/InsetsController.java15
-rw-r--r--core/java/android/view/Surface.java4
-rw-r--r--core/java/android/view/SurfaceControl.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java22
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig10
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java29
-rw-r--r--core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java34
-rw-r--r--core/jni/android_tracing_PerfettoDataSource.cpp2
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java126
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java10
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java188
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java538
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java427
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml3
-rw-r--r--libs/androidfw/ZipFileRO.cpp2
-rw-r--r--libs/dream/lowlight/tests/Android.bp2
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig10
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java15
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/RescueParty.java2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt7
-rw-r--r--packages/SettingsLib/Android.bp4
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig1
-rw-r--r--packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java38
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java57
-rw-r--r--packages/SettingsProvider/Android.bp2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java42
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig1
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/Android.bp17
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/animation/Android.bp4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt166
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt168
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt161
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java6
-rw-r--r--packages/SystemUI/res/anim/slide_in_up.xml21
-rw-r--r--packages/SystemUI/res/anim/slide_out_down.xml21
-rw-r--r--packages/SystemUI/res/drawable/ic_shortcutlist_search.xml7
-rw-r--r--packages/SystemUI/res/drawable/shortcut_button_colored.xml2
-rw-r--r--packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml2
-rw-r--r--packages/SystemUI/res/drawable/shortcut_dialog_bg.xml6
-rw-r--r--packages/SystemUI/res/drawable/shortcut_search_background.xml6
-rw-r--r--packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml2
-rw-r--r--packages/SystemUI/res/layout/clipboard_edit_text_activity.xml1
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml6
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml4
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml4
-rw-r--r--packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml41
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml10
-rw-r--r--packages/SystemUI/res/values/strings.xml12
-rw-r--r--packages/SystemUI/res/values/styles.xml23
-rw-r--r--packages/SystemUI/shared/Android.bp4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl7
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt (renamed from packages/SystemUI/src-debug/com/android/systemui/util/Compile.java)15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt285
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt (renamed from packages/SystemUI/src-release/com/android/systemui/util/Compile.java)18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt2
-rw-r--r--ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java47
-rw-r--r--ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java39
-rw-r--r--ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java43
-rw-r--r--ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java2
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java6
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java26
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java13
-rw-r--r--services/Android.bp35
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java8
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java4
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java4
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java)144
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java)94
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java (renamed from services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java)64
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java (renamed from services/companion/java/com/android/server/companion/presence/ObservableUuid.java)2
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java (renamed from services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java)2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java2
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java25
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java23
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java9
-rw-r--r--services/core/java/com/android/server/audio/AudioManagerShellCommand.java62
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java19
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java75
-rw-r--r--services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java2
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java14
-rw-r--r--services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java36
-rw-r--r--services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java2
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/Convert.java13
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/Tuner.java15
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java17
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java16
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java44
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/Convert.java76
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java6
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java43
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java31
-rw-r--r--services/core/java/com/android/server/devicestate/OWNERS3
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java13
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java703
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java92
-rw-r--r--services/core/java/com/android/server/display/HysteresisLevels.java152
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java15
-rw-r--r--services/core/java/com/android/server/display/NormalBrightnessModeController.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java21
-rw-r--r--services/core/java/com/android/server/display/config/HysteresisLevels.java463
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java26
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java10
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java3
-rw-r--r--services/core/java/com/android/server/media/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java22
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java8
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java7
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java3
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java76
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java14
-rw-r--r--services/core/java/com/android/server/wm/DisplayUpdater.java13
-rw-r--r--services/core/java/com/android/server/wm/Transition.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java7
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java123
-rw-r--r--services/permission/TEST_MAPPING22
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java146
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java35
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java50
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java25
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java75
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING17
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING17
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING20
-rw-r--r--services/tests/servicestests/src/com/android/server/om/TEST_MAPPING13
-rw-r--r--services/tests/servicestests/src/com/android/server/os/TEST_MAPPING11
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING2
-rw-r--r--services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java74
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java9
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java42
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java2
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java32
-rw-r--r--telephony/java/android/telephony/satellite/stub/ISatellite.aidl5
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java9
-rw-r--r--tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java24
-rw-r--r--tests/UsbManagerTests/Android.bp1
-rw-r--r--tests/UsbTests/Android.bp3
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java174
-rw-r--r--tools/hoststubgen/hoststubgen/Android.bp23
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt54
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt (renamed from tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt)12
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt2
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt5
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt11
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt113
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt24
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt46
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt11
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt56
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java2
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java4
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java11
297 files changed, 7341 insertions, 2973 deletions
diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp
index 4db39dc1976b..859c67ad8910 100644
--- a/apex/jobscheduler/service/aconfig/Android.bp
+++ b/apex/jobscheduler/service/aconfig/Android.bp
@@ -2,6 +2,7 @@
aconfig_declarations {
name: "service-deviceidle.flags-aconfig",
package: "com.android.server.deviceidle",
+ container: "system",
srcs: [
"device_idle.aconfig",
],
@@ -17,6 +18,7 @@ java_aconfig_library {
aconfig_declarations {
name: "service-job.flags-aconfig",
package: "com.android.server.job",
+ container: "system",
srcs: [
"job.aconfig",
],
@@ -32,6 +34,7 @@ java_aconfig_library {
aconfig_declarations {
name: "alarm_flags",
package: "com.android.server.alarm",
+ container: "system",
srcs: ["alarm.aconfig"],
}
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index bb0f3cbd5257..d3068d7d37e8 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.alarm"
+container: "system"
flag {
name: "use_frozen_state_to_drop_listener_alarms"
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index e4cb5ad81ba0..e8c99b12828f 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.deviceidle"
+container: "system"
flag {
name: "disable_wakelocks_in_light_idle"
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5e6d3775f6a2..75e2efd2ec99 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.job"
+container: "system"
flag {
name: "batch_active_bucket_jobs"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede274bc1..096238aeda7c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1737,6 +1737,17 @@ class JobConcurrencyManager {
continue;
}
+ if (!nextPending.isReady()) {
+ // This could happen when the job count reached its quota, the constrains
+ // for the job has been updated but hasn't been removed from the pending
+ // queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b20003..cfbfa5dce399 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -512,7 +512,7 @@ public final class QuotaController extends StateController {
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_QUOTA = 0;
+ static final int MSG_REACHED_TIME_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +524,7 @@ public final class QuotaController extends StateController {
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_QUOTA = 4;
+ static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,6 +533,11 @@ public final class QuotaController extends StateController {
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
+ /**
+ * An app has reached its job count quota. The message should contain a {@link UserPackage}
+ * object.
+ */
+ static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +879,37 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
+ @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- return jobStatus.shouldTreatAsUserInitiatedJob()
+ if (jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ || isUidInForeground(jobStatus.getSourceUid())) {
+ return true;
+ }
+
+ if (standbyBucket == NEVER_INDEX) return false;
+
+ if (isQuotaFreeLocked(standbyBucket)) return true;
+
+ final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+ jobStatus.getSourcePackageName(), standbyBucket);
+ if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+ // Out of execution time quota.
+ return false;
+ }
+
+ if (mService.isCurrentlyRunningLocked(jobStatus)) {
+ // if job is running, considered as in quota so it can keep running.
+ return true;
+ }
+
+ // Check if the app is within job count quota.
+ return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
}
@GuardedBy("mLock")
@@ -909,12 +934,11 @@ public final class QuotaController extends StateController {
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+ && isUnderJobCountQuotaLocked(stats)
+ && isUnderSessionCountQuotaLocked(stats);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +947,7 @@ public final class QuotaController extends StateController {
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1472,7 @@ public final class QuotaController extends StateController {
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
+ stats.bgJobCountInWindow += count;
}
}
@@ -1683,10 +1707,11 @@ public final class QuotaController extends StateController {
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && !mService.isCurrentlyRunningLocked(js)) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
+ // individually. Running job need to determine its own quota status as well.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1805,9 +1830,8 @@ public final class QuotaController extends StateController {
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2126,6 +2150,11 @@ public final class QuotaController extends StateController {
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+ final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+ mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+ if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
+ mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+ }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2257,7 +2286,6 @@ public final class QuotaController extends StateController {
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
-
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2284,7 +2312,8 @@ public final class QuotaController extends StateController {
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+ mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2330,7 @@ public final class QuotaController extends StateController {
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2586,7 @@ public final class QuotaController extends StateController {
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping event " + event.getEventType());
+ Slog.d(TAG, "Dropping usage event " + event.getEventType());
}
break;
}
@@ -2666,7 +2695,7 @@ public final class QuotaController extends StateController {
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_QUOTA: {
+ case MSG_REACHED_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2714,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2695,7 +2724,7 @@ public final class QuotaController extends StateController {
}
break;
}
- case MSG_REACHED_EJ_QUOTA: {
+ case MSG_REACHED_EJ_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2742,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2723,6 +2752,18 @@ public final class QuotaController extends StateController {
}
break;
}
+ case MSG_REACHED_COUNT_QUOTA: {
+ UserPackage pkg = (UserPackage) msg.obj;
+ if (DEBUG) {
+ Slog.d(TAG, pkg + " has reached its count quota.");
+ }
+
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(),
+ pkg.userId, pkg.packageName));
+ break;
+ }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/api/Android.bp b/api/Android.bp
index 010a2a587057..3fa9c600741e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -382,6 +382,18 @@ stubs_defaults {
],
}
+soong_config_module_type {
+ name: "non_updatable_exportable_droidstubs",
+ module_type: "droidstubs",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_hidden_api_exportable_stubs",
+ ],
+ properties: [
+ "dists",
+ ],
+}
+
// We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
// platform (sdk_system_current_android). That prebuilt does not include module-lib APIs,
// so use the prebuilt module-lib stubs for modules that export module-lib stubs that the
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index c1add03fa31a..1b1bc6b9afdb 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -27,7 +27,12 @@
// These modules provide source files for the stub libraries
/////////////////////////////////////////////////////////////////////
-droidstubs {
+soong_config_module_type_import {
+ from: "frameworks/base/api/Android.bp",
+ module_types: ["non_updatable_exportable_droidstubs"],
+}
+
+non_updatable_exportable_droidstubs {
name: "api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -54,15 +59,35 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/public/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "public",
}
@@ -86,7 +111,7 @@ module_libs = [
"\\)",
]
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "system-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -114,19 +139,39 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/system/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "system",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "test-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -149,31 +194,61 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "removed.txt",
- tag: ".removed-api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/test/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "test",
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "module-lib-api-stubs-docs-non-updatable",
defaults: [
"android-non-updatable-stubs-defaults",
@@ -201,15 +276,35 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "module-lib",
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b7c2ee920c79..b767c52ea9ba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -319,7 +319,6 @@ package android {
field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
- field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO";
field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final String RECEIVE_SENSITIVE_NOTIFICATIONS = "android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS";
field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8913d6d15dc0..e53bd395f7fe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6982,7 +6982,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
} else {
// No package, perhaps it was removed?
- Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ Slog.d(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ " but missing application info. Assuming REMOVED.");
mPackages.remove(packages[i]);
mResourcePackages.remove(packages[i]);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7ed10e784b1c..7ae514ac2491 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1502,12 +1502,10 @@ public class AppOpsManager {
AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
- * Allows the privileged assistant app to receive the training data from the sandboxed hotword
- * detection service.
+ * This op has been deprecated.
*
- * @hide
*/
- public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
+ private static final int OP_DEPRECATED_3 =
AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/**
@@ -1735,7 +1733,6 @@ public class AppOpsManager {
OPSTR_CAMERA_SANDBOXED,
OPSTR_RECORD_AUDIO_SANDBOXED,
OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
OPSTR_MEDIA_ROUTING_CONTROL,
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
@@ -2395,13 +2392,10 @@ public class AppOpsManager {
"android:receive_sandbox_trigger_audio";
/**
- * Allows the privileged assistant app to receive training data from the sandboxed hotword
- * detection service.
- *
+ * App op has been deprecated.
* @hide
*/
- public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA =
- "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";
+ public static final String OPSTR_DEPRECATED_3 = "android:deprecated_3";
/**
* Creation of an overlay using accessibility services
@@ -2582,7 +2576,6 @@ public class AppOpsManager {
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
OP_USE_FULL_SCREEN_INTENT,
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
- OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OP_RUN_BACKUP_JOBS,
@@ -3021,11 +3014,8 @@ public class AppOpsManager {
"RECEIVE_SANDBOX_TRIGGER_AUDIO")
.setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
.setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
- new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
- OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
- "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA")
- .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
- .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(),
+ new AppOpInfo.Builder(OP_DEPRECATED_3, OPSTR_DEPRECATED_3, "DEPRECATED_3")
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
new AppOpInfo.Builder(OP_CREATE_ACCESSIBILITY_OVERLAY,
OPSTR_CREATE_ACCESSIBILITY_OVERLAY,
"CREATE_ACCESSIBILITY_OVERLAY")
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 42e82f6d77c0..ea6f45e8e201 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10284,6 +10284,16 @@ public class DevicePolicyManager {
* get the list of app restrictions set by each admin via
* {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin}.
*
+ * <p>Starting from Android Version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+ * the device policy management role holder can also set app restrictions on any applications
+ * in the calling user, as well as the parent user of an organization-owned managed profile via
+ * the {@link DevicePolicyManager} instance returned by
+ * {@link #getParentProfileInstance(ComponentName)}. App restrictions set by the device policy
+ * management role holder are not returned by
+ * {@link UserManager#getApplicationRestrictions(String)}. The target application should use
+ * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} to retrieve
+ * them, alongside any app restrictions the profile or device owner might have set.
+ *
* <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
@@ -10299,11 +10309,14 @@ public class DevicePolicyManager {
@WorkerThread
public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
Bundle settings) {
- throwIfParentInstance("setApplicationRestrictions");
+ if (!Flags.dmrhCanSetAppRestriction()) {
+ throwIfParentInstance("setApplicationRestrictions");
+ }
+
if (mService != null) {
try {
mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
- settings);
+ settings, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11704,11 +11717,14 @@ public class DevicePolicyManager {
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@Nullable ComponentName admin, String packageName) {
- throwIfParentInstance("getApplicationRestrictions");
+ if (!Flags.dmrhCanSetAppRestriction()) {
+ throwIfParentInstance("getApplicationRestrictions");
+ }
+
if (mService != null) {
try {
return mService.getApplicationRestrictions(admin, mContext.getPackageName(),
- packageName);
+ packageName, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -13986,8 +14002,15 @@ public class DevicePolicyManager {
public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
throwIfParentInstance("getParentProfileInstance");
try {
- if (!mService.isManagedProfile(admin)) {
- throw new SecurityException("The current user does not have a parent profile.");
+ if (Flags.dmrhCanSetAppRestriction()) {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ if (!um.isManagedProfile()) {
+ throw new SecurityException("The current user does not have a parent profile.");
+ }
+ } else {
+ if (!mService.isManagedProfile(admin)) {
+ throw new SecurityException("The current user does not have a parent profile.");
+ }
}
return new DevicePolicyManager(mContext, mService, true);
} catch (RemoteException e) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d4589dc6d453..2002326d76bd 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -244,8 +244,8 @@ interface IDevicePolicyManager {
void setDefaultSmsApplication(in ComponentName admin, String callerPackageName, String packageName, boolean parent);
void setDefaultDialerApplication(String packageName);
- void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
- Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
+ void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings, in boolean parent);
+ Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in boolean parent);
boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
String getApplicationRestrictionsManagingPackage(in ComponentName admin);
boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6b2baa7ceb61..56fb4aa45fb3 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -204,6 +204,13 @@ flag {
}
flag {
+ name: "dmrh_can_set_app_restriction"
+ namespace: "enterprise"
+ description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
+ bug: "328758346"
+}
+
+flag {
name: "allow_screen_brightness_control_on_cope"
namespace: "enterprise"
description: "Allow COPE admin to control screen brightness and timeout."
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 90b7869e1c5d..a0e40f6390ee 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -52,6 +52,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -70,7 +71,8 @@ import javax.crypto.Mac;
public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
private static final String TAG = "BiometricPrompt";
- private static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
+ @VisibleForTesting
+ static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30;
/**
* Error/help message will show for this amount of time.
@@ -223,8 +225,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
*
* @param logoDescription The logo description text that will be shown on the prompt.
* @return This builder.
- * @throws IllegalStateException If logo description is null or exceeds certain character
- * limit.
+ * @throws IllegalArgumentException If logo description is null or exceeds certain character
+ * limit.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@@ -232,7 +234,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
if (logoDescription == null
|| logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"Logo description passed in can not be null or exceed "
+ MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number.");
}
@@ -240,7 +242,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
return this;
}
-
/**
* Required: Sets the title that will be shown on the prompt.
* @param title The title to display.
diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
index 853d86cf94dc..a9eca3f87fc3 100644
--- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
+++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java
@@ -29,19 +29,22 @@ import android.hardware.biometrics.BiometricPrompt.ButtonInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.concurrent.Executor;
/**
- * Contains the information of the template of content view with a more options button for Biometric
- * Prompt.
+ * Contains the information of the template of content view with a more options button for
+ * Biometric Prompt.
+ * <p>
* This button should be used to provide more options for sign in or other purposes, such as when a
* user needs to select between multiple app-specific accounts or profiles that are available for
- * sign in. This is not common and apps should avoid using it if there is only one choice available
- * or if the user has already selected the appropriate account to use before invoking
- * BiometricPrompt because it will create additional steps that the user must navigate through.
- * Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask
- * the user for the correct account, and finally allow the app to decide how to proceed once
- * selected.
+ * sign in.
+ * <p>
+ * Apps should avoid using this when possible because it will create additional steps that the user
+ * must navigate through - clicking the more options button will dismiss the prompt, provide the app
+ * an opportunity to ask the user for the correct option, and finally allow the app to decide how to
+ * proceed once selected.
*
* <p>
* Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric
@@ -59,7 +62,8 @@ import java.util.concurrent.Executor;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable {
- private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
+ @VisibleForTesting
+ static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
private final String mDescription;
private DialogInterface.OnClickListener mListener;
@@ -132,14 +136,16 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
}
};
+ /**
+ * A builder that collects arguments to be shown on the content view with more options button.
+ */
public static final class Builder {
private String mDescription;
private Executor mExecutor;
private DialogInterface.OnClickListener mListener;
/**
- * Optional: Sets a description that will be shown on the content view. Note that there are
- * limits on the number of characters allowed for description.
+ * Optional: Sets a description that will be shown on the content view.
*
* @param description The description to display.
* @return This builder.
@@ -149,7 +155,7 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
public Builder setDescription(@NonNull String description) {
if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException("The character number of description exceeds "
+ throw new IllegalArgumentException("The character number of description exceeds "
+ MAX_DESCRIPTION_CHARACTER_NUMBER);
}
mDescription = description;
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index 02b2a50ade3c..d8b28673f8ae 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -24,6 +24,8 @@ import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
@@ -47,9 +49,12 @@ import java.util.List;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptVerticalListContentView implements PromptContentViewParcelable {
- private static final int MAX_ITEM_NUMBER = 20;
- private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
- private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
+ @VisibleForTesting
+ static final int MAX_ITEM_NUMBER = 20;
+ @VisibleForTesting
+ static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
+ @VisibleForTesting
+ static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225;
private final List<PromptContentItemParcelable> mContentList;
private final String mDescription;
@@ -155,7 +160,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
@NonNull
public Builder setDescription(@NonNull String description) {
if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalStateException("The character number of description exceeds "
+ throw new IllegalArgumentException("The character number of description exceeds "
+ MAX_DESCRIPTION_CHARACTER_NUMBER);
}
mDescription = description;
@@ -195,12 +200,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
private void checkItemLimits(@NonNull PromptContentItem listItem) {
if (doesListItemExceedsCharLimit(listItem)) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"The character number of list item exceeds "
+ MAX_EACH_ITEM_CHARACTER_NUMBER);
}
if (mContentList.size() > MAX_ITEM_NUMBER) {
- throw new IllegalStateException(
+ throw new IllegalArgumentException(
"The number of list items exceeds " + MAX_ITEM_NUMBER);
}
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ec9b013c34cc..dca663d206d3 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -23,12 +23,14 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.graphics.SurfaceTexture;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
+import android.util.Size;
import android.view.Surface;
import com.android.internal.camera.flags.Flags;
@@ -530,9 +532,10 @@ public abstract class CameraDevice implements AutoCloseable {
* SurfaceTexture}: Set the size of the SurfaceTexture with {@link
* android.graphics.SurfaceTexture#setDefaultBufferSize} to be one of the sizes returned by
* {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(SurfaceTexture.class)}
- * before creating a Surface from the SurfaceTexture with {@link Surface#Surface}. If the size
- * is not set by the application, it will be set to be the smallest supported size less than
- * 1080p, by the camera device.</li>
+ * before creating a Surface from the SurfaceTexture with
+ * {@link Surface#Surface(SurfaceTexture)}. If the size is not set by the application,
+ * it will be set to be the smallest supported size less than 1080p, by the camera
+ * device.</li>
*
* <li>For recording with {@link android.media.MediaCodec}: Call
* {@link android.media.MediaCodec#createInputSurface} after configuring
@@ -1405,10 +1408,16 @@ public abstract class CameraDevice implements AutoCloseable {
*
* <p><b>NOTE:</b>
* For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,
- * this method will ensure session parameters set through calls to
- * {@link SessionConfiguration#setSessionParameters} are also supported if the Camera Device
- * supports it. For apps targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and
- * below, session parameters will be ignored.</p>
+ * this method will automatically delegate to
+ * {@link CameraDeviceSetup#isSessionConfigurationSupported} whenever possible. This
+ * means that the output of this method will consider parameters set through
+ * {@link SessionConfiguration#setSessionParameters} as well.
+ * </p>
+ *
+ * <p>Session Parameters will be ignored for apps targeting <=
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, or if
+ * {@link CameraManager#isCameraDeviceSetupSupported} returns false for the camera id
+ * associated with this {@code CameraDevice}.</p>
*
* @return {@code true} if the given session configuration is supported by the camera device
* {@code false} otherwise.
@@ -1419,6 +1428,8 @@ public abstract class CameraDevice implements AutoCloseable {
* encountered a fatal error
* @throws IllegalStateException if the camera device has been closed
*
+ * @see CameraManager#isCameraDeviceSetupSupported(String)
+ * @see CameraDeviceSetup#isSessionConfigurationSupported(SessionConfiguration)
*/
public boolean isSessionConfigurationSupported(
@NonNull SessionConfiguration sessionConfig) throws CameraAccessException {
@@ -1703,7 +1714,7 @@ public abstract class CameraDevice implements AutoCloseable {
* SessionConfiguration} can then be created using the OutputConfiguration objects and
* be used to query whether it's supported by the camera device. To create the
* CameraCaptureSession, the application still needs to make sure all output surfaces
- * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred
+ * are added via {@link OutputConfiguration#addSurface} with the exception of deferred
* surfaces for {@link android.view.SurfaceView} and
* {@link android.graphics.SurfaceTexture}.</li>
* </ul>
@@ -1751,7 +1762,7 @@ public abstract class CameraDevice implements AutoCloseable {
* SessionConfiguration} can then be created using the OutputConfiguration objects and
* be used for this function. To create the CameraCaptureSession, the application still
* needs to make sure all output surfaces are added via {@link
- * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link
+ * OutputConfiguration#addSurface} with the exception of deferred surfaces for {@link
* android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p>
*
* @param sessionConfig The session configuration for which characteristics are fetched.
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 64fc4c29db90..e583627c0960 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -399,16 +399,8 @@ public final class DeviceState {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mIdentifier);
dest.writeString8(mName);
-
- dest.writeInt(mSystemProperties.size());
- for (int i = 0; i < mSystemProperties.size(); i++) {
- dest.writeInt(mSystemProperties.valueAt(i));
- }
-
- dest.writeInt(mPhysicalProperties.size());
- for (int i = 0; i < mPhysicalProperties.size(); i++) {
- dest.writeInt(mPhysicalProperties.valueAt(i));
- }
+ dest.writeArraySet(mSystemProperties);
+ dest.writeArraySet(mPhysicalProperties);
}
@NonNull
@@ -417,16 +409,11 @@ public final class DeviceState {
public DeviceState.Configuration createFromParcel(Parcel source) {
int identifier = source.readInt();
String name = source.readString8();
- ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
- int systemPropertySize = source.readInt();
- for (int i = 0; i < systemPropertySize; i++) {
- systemProperties.add(source.readInt());
- }
- ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
- int physicalPropertySize = source.readInt();
- for (int j = 0; j < physicalPropertySize; j++) {
- physicalProperties.add(source.readInt());
- }
+ ArraySet<@SystemDeviceStateProperties Integer> systemProperties =
+ (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+ ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties =
+ (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+
return new DeviceState.Configuration(identifier, name, systemProperties,
physicalProperties);
}
diff --git a/core/java/android/hardware/devicestate/OWNERS b/core/java/android/hardware/devicestate/OWNERS
new file mode 100644
index 000000000000..d9b0e2e5ffa5
--- /dev/null
+++ b/core/java/android/hardware/devicestate/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/devicestate/OWNERS
diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING
index 69113ef8f946..a15d9bc1b485 100644
--- a/core/java/android/permission/TEST_MAPPING
+++ b/core/java/android/permission/TEST_MAPPING
@@ -11,5 +11,29 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsVirtualDevicesAudioTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsVirtualDevicesAppLaunchTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
+ }
+ ]
+ }
]
} \ No newline at end of file
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 4e521d62684a..353828c105bb 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -17,6 +17,7 @@
package android.service.dreams;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import android.annotation.FlaggedApi;
import android.annotation.IdRes;
@@ -29,6 +30,7 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.AlarmManager;
+import android.app.KeyguardManager;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -280,6 +282,8 @@ public class DreamService extends Service implements Window.Callback {
private IDreamOverlayCallback mOverlayCallback;
+ private Integer mTrackingConfirmKey = null;
+
public DreamService() {
mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
@@ -296,7 +300,54 @@ public class DreamService extends Service implements Window.Callback {
/** {@inheritDoc} */
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
+ if (dreamHandlesConfirmKeys()) {
+ // In the case of an interactive dream that consumes the event, do not process further.
+ if (mInteractive && mWindow.superDispatchKeyEvent(event)) {
+ return true;
+ }
+
+ // If the key is a confirm key and on up, either unlock (no auth) or show bouncer.
+ if (KeyEvent.isConfirmKey(event.getKeyCode())) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN -> {
+ if (mTrackingConfirmKey != null) {
+ return true;
+ }
+
+ mTrackingConfirmKey = event.getKeyCode();
+ }
+ case KeyEvent.ACTION_UP -> {
+ if (mTrackingConfirmKey != event.getKeyCode()) {
+ return true;
+ }
+
+ mTrackingConfirmKey = null;
+
+ final KeyguardManager keyguardManager =
+ getSystemService(KeyguardManager.class);
+
+ // Simply wake up in the case the device is not locked.
+ if (!keyguardManager.isKeyguardLocked()) {
+ wakeUp();
+ return true;
+ }
+
+ keyguardManager.requestDismissKeyguard(getActivity(),
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissError() {
+ Log.e(TAG, "Could not dismiss keyguard on confirm key");
+ }
+ });
+ }
+ }
+
+ // All key events for matching key codes should be consumed to prevent other actions
+ // from triggering.
+ return true;
+ }
+ }
+
if (!mInteractive) {
if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
wakeUp();
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 2e16a036618d..2f45f34b8da2 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -8,3 +8,14 @@ flag {
"relying on the dream's window"
bug: "291990564"
}
+
+flag {
+ name: "dream_handles_confirm_keys"
+ namespace: "dreams"
+ description: "This flag enables dreams processing confirm keys to show the bouncer or dismiss "
+ "the keyguard"
+ bug: "326975875"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2402a0b16b16..b52003f437da 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -31,7 +31,6 @@ import static android.view.WindowInsets.Type.ime;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
@@ -69,7 +68,6 @@ import android.view.inputmethod.ImeTracker.InputMethodJankContext;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.util.function.TriFunction;
@@ -379,16 +377,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final WindowInsetsAnimationControlListener mLoggingListener;
private final InputMethodJankContext mInputMethodJankContext;
- private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
- new ThreadLocal<AnimationHandler>() {
- @Override
- protected AnimationHandler initialValue() {
- AnimationHandler handler = new AnimationHandler();
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- return handler;
- }
- };
-
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
@@ -470,9 +458,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
ImeTracker.forJank().onFinishAnimation(getAnimationType());
}
});
- if (!mHasAnimationCallbacks) {
- mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
- }
mAnimator.start();
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6c6e8b247886..188ad8f7e47c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -203,9 +203,7 @@ public class Surface implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
- value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
- FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
- FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
+ value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
public @interface FrameRateCompatibility {}
// From native_window.h. Keep these in sync.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cfdf8fab05c2..1cd7d349a9af 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1272,7 +1272,7 @@ public final class SurfaceControl implements Parcelable {
* surface has no buffer or crop, the surface is boundless and only constrained
* by the size of its parent bounds.
*
- * @param session The surface session, must not be null.
+ * @param session The surface session.
* @param name The surface name, must not be null.
* @param w The surface initial width.
* @param h The surface initial height.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c8cb1a7c8773..db1b73f5352d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -110,6 +110,7 @@ import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -1153,6 +1154,7 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
+ private static final boolean sToolkitFrameRateViewEnablingReadOnlyFlagValue;
private static boolean sToolkitFrameRateVelocityMappingReadOnlyFlagValue =
toolkitFrameRateVelocityMappingReadOnly();;
@@ -1162,6 +1164,8 @@ public final class ViewRootImpl implements ViewParent,
sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
sToolkitFrameRateFunctionEnablingReadOnlyFlagValue =
toolkitFrameRateFunctionEnablingReadOnly();
+ sToolkitFrameRateViewEnablingReadOnlyFlagValue =
+ toolkitFrameRateViewEnablingReadOnly();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -2624,8 +2628,10 @@ public final class ViewRootImpl implements ViewParent,
// no longer needed if the dVRR feature is disabled.
if (shouldEnableDvrr()) {
try {
- mFrameRateTransaction.setFrameRateSelectionStrategy(sc,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRateSelectionStrategy(sc,
sc.FRAME_RATE_SELECTION_STRATEGY_SELF).applyAsyncUnsafe();
+ }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate selection strategy ", e);
}
@@ -12536,9 +12542,11 @@ public final class ViewRootImpl implements ViewParent,
+ category + ", reason " + reason + ", "
+ sourceView);
}
- mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
- mLastPreferredFrameRateCategory = frameRateCategory;
+ mLastPreferredFrameRateCategory = frameRateCategory;
+ }
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate category", e);
@@ -12595,9 +12603,11 @@ public final class ViewRootImpl implements ViewParent,
+ preferredFrameRate + " compatibility "
+ mFrameRateCompatibility);
}
- mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
+ if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
mFrameRateCompatibility).applyAsyncUnsafe();
- mLastPreferredFrameRate = preferredFrameRate;
+ mLastPreferredFrameRate = preferredFrameRate;
+ }
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
@@ -12824,7 +12834,7 @@ public final class ViewRootImpl implements ViewParent,
private boolean shouldEnableDvrr() {
// uncomment this when we are ready for enabling dVRR
- if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
+ if (sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
}
return false;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 4402ac712d42..dd6b772ab871 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -9,6 +9,16 @@ flag {
}
flag {
+ name: "wait_for_transition_on_display_switch"
+ namespace: "windowing_frontend"
+ description: "Waits for Shell transition to start before unblocking the screen after display switch"
+ bug: "301420598"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 9481dc91bcc4..a0c405e31e79 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -53,7 +53,6 @@ import android.util.Slog;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.widget.Toast;
import com.android.internal.R;
@@ -369,23 +368,17 @@ public class AccessibilityShortcutController {
})
.setPositiveButton(R.string.accessibility_shortcut_off,
(DialogInterface d, int which) -> {
- if (Flags.updateAlwaysOnA11yService()) {
- Set<String> targetServices =
- ShortcutUtils.getShortcutTargetsFromSettings(
- mContext,
- HARDWARE,
- userId);
-
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- mContext, targetServices, userId);
- } else {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- }
+ Set<String> targetServices =
+ ShortcutUtils.getShortcutTargetsFromSettings(
+ mContext,
+ HARDWARE,
+ userId);
+
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+ userId);
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, targetServices, userId);
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 7831afb8798e..209778808764 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,17 +16,11 @@
package com.android.internal.accessibility.dialog;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
-
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
-import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -53,31 +47,9 @@ public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServ
@Override
public void onCheckedChanged(boolean isChecked) {
+ super.onCheckedChanged(isChecked);
final ComponentName componentName = ComponentName.unflattenFromString(getId());
-
- if (Flags.updateAlwaysOnA11yService()) {
- super.onCheckedChanged(isChecked);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId());
- } else {
- if (!isComponentIdExistingInOtherShortcut()) {
- setAccessibilityServiceState(getContext(), componentName, isChecked);
- }
-
- super.onCheckedChanged(isChecked);
- }
- }
-
- private boolean isComponentIdExistingInOtherShortcut() {
- switch (getShortcutType()) {
- case SOFTWARE:
- return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
- getId());
- case HARDWARE:
- return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
- getId());
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId());
}
}
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 1eff5ce8eaa3..25ff853ae7e4 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -213,7 +213,7 @@ void PerfettoDataSource::flushAll() {
PerfettoDataSource::~PerfettoDataSource() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mJavaDataSource);
+ env->DeleteGlobalRef(mJavaDataSource);
}
jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d4256ca316c2..f74329903690 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7507,16 +7507,6 @@
<permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
android:protectionLevel="signature|privileged|appop" />
- <!-- @SystemApi Required for the privileged assistant apps targeting
- {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
- that receive training data from a sandboxed {@link HotwordDetectionService} or
- {@link VisualQueryDetectionService}.
- <p>Protection level: internal|appop
- @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
- @hide -->
- <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
- android:protectionLevel="internal|appop" />
-
<!-- @SystemApi Allows requesting the framework broadcast the
{@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
@hide -->
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
index 66f3bca72aeb..ca9154280a10 100644
--- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -16,6 +16,14 @@
package android.hardware.biometrics;
+import static android.hardware.biometrics.BiometricPrompt.MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.MAX_DESCRIPTION_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptVerticalListContentView.MAX_EACH_ITEM_CHARACTER_NUMBER;
+import static android.hardware.biometrics.PromptVerticalListContentView.MAX_ITEM_NUMBER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -40,6 +48,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.MockitoRule;
+import java.util.Random;
import java.util.concurrent.Executor;
@@ -83,10 +92,11 @@ public class BiometricPromptTest {
ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
BiometricPrompt.AuthenticationCallback callback =
new BiometricPrompt.AuthenticationCallback() {
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- super.onAuthenticationError(errorCode, errString);
- }};
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }
+ };
mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
mLooper.dispatchAll();
@@ -99,4 +109,112 @@ public class BiometricPromptTest {
verify(mService).cancelAuthentication(any(), anyString(), anyLong());
}
+
+ @Test
+ public void testLogoDescription_null() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new BiometricPrompt.Builder(mContext).setLogoDescription(null)
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "Logo description passed in can not be null or exceed");
+ }
+
+ @Test
+ public void testLogoDescription_charLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new BiometricPrompt.Builder(mContext).setLogoDescription(
+ generateRandomString(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "Logo description passed in can not be null or exceed");
+ }
+
+ @Test
+ public void testMoreOptionsButton_descriptionCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptContentViewWithMoreOptionsButton.Builder().setDescription(
+ generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of description exceeds ");
+ }
+
+ @Test
+ public void testMoreOptionsButton_ExecutorNull() {
+ PromptContentViewWithMoreOptionsButton.Builder builder =
+ new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener(
+ null, null);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ builder::build
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The executor for the listener of more options button on prompt content must be "
+ + "set");
+ }
+
+ @Test
+ public void testMoreOptionsButton_ListenerNull() {
+ PromptContentViewWithMoreOptionsButton.Builder builder =
+ new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener(
+ mExecutor, null);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ builder::build
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The listener of more options button on prompt content must be set");
+ }
+
+ @Test
+ public void testVerticalList_descriptionCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptVerticalListContentView.Builder().setDescription(
+ generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of description exceeds ");
+ }
+
+ @Test
+ public void testVerticalList_itemCharLimit() {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> new PromptVerticalListContentView.Builder().addListItem(
+ new PromptContentItemBulletedText(
+ generateRandomString(MAX_EACH_ITEM_CHARACTER_NUMBER + 1)))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The character number of list item exceeds ");
+ }
+
+ @Test
+ public void testVerticalList_itemNumLimit() {
+ PromptVerticalListContentView.Builder builder = new PromptVerticalListContentView.Builder();
+
+ for (int i = 0; i < MAX_ITEM_NUMBER; i++) {
+ builder.addListItem(new PromptContentItemBulletedText(generateRandomString(10)));
+ }
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> builder.addListItem(
+ new PromptContentItemBulletedText(generateRandomString(10)))
+ );
+
+ assertThat(e).hasMessageThat().contains(
+ "The number of list items exceeds ");
+ }
+
+ private String generateRandomString(int charNum) {
+ final Random random = new Random();
+ final StringBuilder longString = new StringBuilder(charNum);
+ for (int j = 0; j < charNum; j++) {
+ longString.append(random.nextInt(10));
+ }
+ return longString.toString();
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 180521ba7b70..365f3485d65c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,9 +64,6 @@ import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
-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.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -74,7 +71,6 @@ import android.test.mock.MockContentResolver;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Toast;
@@ -87,7 +83,6 @@ import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -103,9 +98,6 @@ import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -440,7 +432,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -452,7 +443,6 @@ public class AccessibilityShortcutControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index b749a06bd516..5c978e21b9bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -210,7 +210,7 @@ android_library {
"androidx.recyclerview_recyclerview",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index fa6dd3914ddd..bf654d979856 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -282,6 +282,6 @@
<string name="expand_menu_text">Open Menu</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
- <!-- Maximize menu maximize button string. -->
+ <!-- Maximize menu snap buttons string. -->
<string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
new file mode 100644
index 000000000000..03547a55fa27
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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.pip2.phone;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.InputEvent;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages the input consumer that allows the Shell to directly receive input.
+ */
+public class PipInputConsumer {
+
+ private static final String TAG = PipInputConsumer.class.getSimpleName();
+
+ /**
+ * Listener interface for callers to subscribe to input events.
+ */
+ public interface InputListener {
+ /** Handles any input event. */
+ boolean onInputEvent(InputEvent ev);
+ }
+
+ /**
+ * Listener interface for callers to learn when this class is registered or unregistered with
+ * window manager
+ */
+ private interface RegistrationListener {
+ void onRegistrationChanged(boolean isRegistered);
+ }
+
+ /**
+ * Input handler used for the input consumer. Input events are batched and consumed with the
+ * SurfaceFlinger vsync.
+ */
+ private final class InputEventReceiver extends BatchedInputEventReceiver {
+
+ InputEventReceiver(InputChannel inputChannel, Looper looper,
+ Choreographer choreographer) {
+ super(inputChannel, looper, choreographer);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = true;
+ try {
+ if (mListener != null) {
+ handled = mListener.onInputEvent(event);
+ }
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+ }
+
+ private final IWindowManager mWindowManager;
+ private final IBinder mToken;
+ private final String mName;
+ private final ShellExecutor mMainExecutor;
+
+ private InputEventReceiver mInputEventReceiver;
+ private InputListener mListener;
+ private RegistrationListener mRegistrationListener;
+
+ /**
+ * @param name the name corresponding to the input consumer that is defined in the system.
+ */
+ public PipInputConsumer(IWindowManager windowManager, String name,
+ ShellExecutor mainExecutor) {
+ mWindowManager = windowManager;
+ mToken = new Binder();
+ mName = name;
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * Sets the input listener.
+ */
+ public void setInputListener(InputListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Sets the registration listener.
+ */
+ public void setRegistrationListener(RegistrationListener listener) {
+ mRegistrationListener = listener;
+ mMainExecutor.execute(() -> {
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
+ }
+ });
+ }
+
+ /**
+ * Check if the InputConsumer is currently registered with WindowManager
+ *
+ * @return {@code true} if registered, {@code false} if not.
+ */
+ public boolean isRegistered() {
+ return mInputEventReceiver != null;
+ }
+
+ /**
+ * Registers the input consumer.
+ */
+ public void registerInputConsumer() {
+ if (mInputEventReceiver != null) {
+ return;
+ }
+ final InputChannel inputChannel = new InputChannel();
+ try {
+ // TODO(b/113087003): Support Picture-in-picture in multi-display.
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
+ mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to create input consumer, %s", TAG, e);
+ }
+ mMainExecutor.execute(() -> {
+ mInputEventReceiver = new InputEventReceiver(inputChannel,
+ Looper.myLooper(), Choreographer.getInstance());
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
+ }
+ });
+ }
+
+ /**
+ * Unregisters the input consumer.
+ */
+ public void unregisterInputConsumer() {
+ if (mInputEventReceiver == null) {
+ return;
+ }
+ try {
+ // TODO(b/113087003): Support Picture-in-picture in multi-display.
+ mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY);
+ } catch (RemoteException e) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to destroy input consumer, %s", TAG, e);
+ }
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ mMainExecutor.execute(() -> {
+ if (mRegistrationListener != null) {
+ mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
+ }
+ });
+ }
+
+ /**
+ * Dumps the {@link PipInputConsumer} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
new file mode 100644
index 000000000000..04cf350ddd3e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2020 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.pip2.phone;
+
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.os.Looper;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
+ * trigger dynamic resize.
+ */
+public class PipResizeGestureHandler {
+
+ private static final String TAG = "PipResizeGestureHandler";
+ private static final int PINCH_RESIZE_SNAP_DURATION = 250;
+ private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+
+ private final Context mContext;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipBoundsState mPipBoundsState;
+ private final PipTouchState mPipTouchState;
+ private final PhonePipMenuController mPhonePipMenuController;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
+ private final int mDisplayId;
+ private final ShellExecutor mMainExecutor;
+
+ private final PointF mDownPoint = new PointF();
+ private final PointF mDownSecondPoint = new PointF();
+ private final PointF mLastPoint = new PointF();
+ private final PointF mLastSecondPoint = new PointF();
+ private final Point mMaxSize = new Point();
+ private final Point mMinSize = new Point();
+ private final Rect mLastResizeBounds = new Rect();
+ private final Rect mUserResizeBounds = new Rect();
+ private final Rect mDownBounds = new Rect();
+ private final Runnable mUpdateMovementBoundsRunnable;
+ private final Consumer<Rect> mUpdateResizeBoundsCallback;
+
+ private float mTouchSlop;
+
+ private boolean mAllowGesture;
+ private boolean mIsAttached;
+ private boolean mIsEnabled;
+ private boolean mEnablePinchResize;
+ private boolean mIsSysUiStateValid;
+ private boolean mThresholdCrossed;
+ private boolean mOngoingPinchToResize = false;
+ private float mAngle = 0;
+ int mFirstIndex = -1;
+ int mSecondIndex = -1;
+
+ private InputMonitor mInputMonitor;
+ private InputEventReceiver mInputEventReceiver;
+
+ @Nullable
+ private final PipPerfHintController mPipPerfHintController;
+
+ @Nullable
+ private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private int mCtrlType;
+ private int mOhmOffset;
+
+ public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+ Runnable updateMovementBoundsRunnable,
+ PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
+ ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+ mContext = context;
+ mDisplayId = context.getDisplayId();
+ mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintController;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipBoundsState = pipBoundsState;
+ mPipTouchState = pipTouchState;
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ mPhonePipMenuController = menuActivityController;
+ mPipUiEventLogger = pipUiEventLogger;
+ mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
+
+ mUpdateResizeBoundsCallback = (rect) -> {
+ mUserResizeBounds.set(rect);
+ // mMotionHelper.synchronizePinnedStackBounds();
+ mUpdateMovementBoundsRunnable.run();
+ resetState();
+ };
+ }
+
+ void init() {
+ mContext.getDisplay().getRealSize(mMaxSize);
+ reloadResources();
+
+ final Resources res = mContext.getResources();
+ mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
+ }
+
+ void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mIsSysUiStateValid = isSysUiStateValid;
+ }
+
+ private void reloadResources() {
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ }
+
+ private void disposeInputChannel() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ }
+
+ void onActivityPinned() {
+ mIsAttached = true;
+ updateIsEnabled();
+ }
+
+ void onActivityUnpinned() {
+ mIsAttached = false;
+ mUserResizeBounds.setEmpty();
+ updateIsEnabled();
+ }
+
+ private void updateIsEnabled() {
+ boolean isEnabled = mIsAttached;
+ if (isEnabled == mIsEnabled) {
+ return;
+ }
+ mIsEnabled = isEnabled;
+ disposeInputChannel();
+
+ if (mIsEnabled) {
+ // Register input event receiver
+ mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
+ "pip-resize", mDisplayId);
+ try {
+ mMainExecutor.executeBlocking(() -> {
+ mInputEventReceiver = new PipResizeInputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ });
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to create input event receiver", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void onInputEvent(InputEvent ev) {
+ if (!mEnablePinchResize) {
+ // No need to handle anything if neither form of resizing is enabled.
+ return;
+ }
+
+ if (!mPipTouchState.getAllowInputEvents()) {
+ // No need to handle anything if touches are not enabled
+ return;
+ }
+
+ // Don't allow resize when PiP is stashed.
+ if (mPipBoundsState.isStashed()) {
+ return;
+ }
+
+ if (ev instanceof MotionEvent) {
+ MotionEvent mv = (MotionEvent) ev;
+ int action = mv.getActionMasked();
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ if (!pipBounds.contains((int) mv.getRawX(), (int) mv.getRawY())
+ && mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+ }
+
+ if (mEnablePinchResize && mOngoingPinchToResize) {
+ onPinchResize(mv);
+ }
+ }
+ }
+
+ /**
+ * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize.
+ */
+ public boolean hasOngoingGesture() {
+ return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
+ }
+
+ public boolean isUsingPinchToZoom() {
+ return mEnablePinchResize;
+ }
+
+ public boolean isResizing() {
+ return mAllowGesture;
+ }
+
+ boolean willStartResizeGesture(MotionEvent ev) {
+ if (isInValidSysUiState()) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isInValidSysUiState() {
+ return mIsSysUiStateValid;
+ }
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ @VisibleForTesting
+ void onPinchResize(MotionEvent ev) {
+ int action = ev.getActionMasked();
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mFirstIndex = -1;
+ mSecondIndex = -1;
+ mAllowGesture = false;
+ finishResize();
+ cleanUpHighPerfSessionMaybe();
+ }
+
+ if (ev.getPointerCount() != 2) {
+ return;
+ }
+
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mFirstIndex == -1 && mSecondIndex == -1
+ && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
+ && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
+ mAllowGesture = true;
+ mFirstIndex = 0;
+ mSecondIndex = 1;
+ mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
+ mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
+ mDownBounds.set(pipBounds);
+
+ mLastPoint.set(mDownPoint);
+ mLastSecondPoint.set(mLastSecondPoint);
+ mLastResizeBounds.set(mDownBounds);
+
+ // start the high perf session as the second pointer gets detected
+ if (mPipPerfHintController != null) {
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "onPinchResize");
+ }
+ }
+ }
+
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (mFirstIndex == -1 || mSecondIndex == -1) {
+ return;
+ }
+
+ float x0 = ev.getRawX(mFirstIndex);
+ float y0 = ev.getRawY(mFirstIndex);
+ float x1 = ev.getRawX(mSecondIndex);
+ float y1 = ev.getRawY(mSecondIndex);
+ mLastPoint.set(x0, y0);
+ mLastSecondPoint.set(x1, y1);
+
+ // Capture inputs
+ if (!mThresholdCrossed
+ && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop
+ || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) {
+ pilferPointers();
+ mThresholdCrossed = true;
+ // Reset the down to begin resizing from this point
+ mDownPoint.set(mLastPoint);
+ mDownSecondPoint.set(mLastSecondPoint);
+
+ if (mPhonePipMenuController.isMenuVisible()) {
+ mPhonePipMenuController.hideMenu();
+ }
+ }
+
+ if (mThresholdCrossed) {
+ mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
+ mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
+ mDownBounds, mLastResizeBounds);
+
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
+ mAngle, null);
+ */
+ mPipBoundsState.setHasUserResizedPip(true);
+ }
+ }
+ }
+
+ private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
+ final int leftEdge = bounds.left;
+
+
+ final int fromLeft = Math.abs(leftEdge - movementBounds.left);
+ final int fromRight = Math.abs(movementBounds.right - leftEdge);
+
+ // The PIP will be snapped to either the right or left edge, so calculate which one
+ // is closest to the current position.
+ final int newLeft = fromLeft < fromRight
+ ? movementBounds.left : movementBounds.right;
+
+ bounds.offsetTo(newLeft, mLastResizeBounds.top);
+ }
+
+ /**
+ * Resizes the pip window and updates user-resized bounds.
+ *
+ * @param bounds target bounds to resize to
+ * @param snapFraction snap fraction to apply after resizing
+ */
+ void userResizeTo(Rect bounds, float snapFraction) {
+ Rect finalBounds = new Rect(bounds);
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds);
+
+ // snap the target bounds to the either left or right edge, by choosing the closer one
+ snapToMovementBoundsEdge(finalBounds, movementBounds);
+
+ // apply the requested snap fraction onto the target bounds
+ mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction);
+
+ // resize from current bounds to target bounds without animation
+ // mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null);
+ // set the flag that pip has been resized
+ mPipBoundsState.setHasUserResizedPip(true);
+
+ // finish the resize operation and update the state of the bounds
+ // mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
+ }
+
+ private void finishResize() {
+ if (!mLastResizeBounds.isEmpty()) {
+ // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
+ // position correctly. Drag-resize does not need to move, so just finalize resize.
+ if (mOngoingPinchToResize) {
+ final Rect startBounds = new Rect(mLastResizeBounds);
+ // If user resize is pretty close to max size, just auto resize to max.
+ if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+ || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+ }
+
+ // If user resize is smaller than min size, auto resize to min
+ if (mLastResizeBounds.width() < mMinSize.x
+ || mLastResizeBounds.height() < mMinSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ }
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm
+ .getMovementBounds(mLastResizeBounds);
+
+ // snap mLastResizeBounds to the correct edge based on movement bounds
+ snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mLastResizeBounds, movementBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // disable any touch events beyond resizing too
+ mPipTouchState.setAllowInputEvents(false);
+
+ /*
+ mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
+ PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
+ // enable touch events
+ mPipTouchState.setAllowInputEvents(true);
+ });
+ */
+ } else {
+ /*
+ mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
+ TRANSITION_DIRECTION_USER_RESIZE,
+ mUpdateResizeBoundsCallback);
+ */
+ }
+ final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
+ } else {
+ resetState();
+ }
+ }
+
+ private void resetState() {
+ mCtrlType = CTRL_NONE;
+ mAngle = 0;
+ mOngoingPinchToResize = false;
+ mAllowGesture = false;
+ mThresholdCrossed = false;
+ }
+
+ void setUserResizeBounds(Rect bounds) {
+ mUserResizeBounds.set(bounds);
+ }
+
+ void invalidateUserResizeBounds() {
+ mUserResizeBounds.setEmpty();
+ }
+
+ Rect getUserResizeBounds() {
+ return mUserResizeBounds;
+ }
+
+ @VisibleForTesting
+ Rect getLastResizeBounds() {
+ return mLastResizeBounds;
+ }
+
+ @VisibleForTesting
+ void pilferPointers() {
+ mInputMonitor.pilferPointers();
+ }
+
+
+ void updateMaxSize(int maxX, int maxY) {
+ mMaxSize.set(maxX, maxY);
+ }
+
+ void updateMinSize(int minX, int minY) {
+ mMinSize.set(minX, minY);
+ }
+
+ void setOhmOffset(int offset) {
+ mOhmOffset = offset;
+ }
+
+ private float distanceBetween(PointF p1, PointF p2) {
+ return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
+ }
+
+ private void resizeRectAboutCenter(Rect rect, int w, int h) {
+ int cx = rect.centerX();
+ int cy = rect.centerY();
+ int l = cx - w / 2;
+ int r = l + w;
+ int t = cy - h / 2;
+ int b = t + h;
+ rect.set(l, t, r, b);
+ }
+
+ /**
+ * Dumps the {@link PipResizeGestureHandler} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
+ pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
+ pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
+ pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
+ pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
+ pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
+ pw.println(innerPrefix + "mMinSize=" + mMinSize);
+ pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
+ }
+
+ class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
+ PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper, Choreographer.getInstance());
+ }
+
+ public void onInputEvent(InputEvent event) {
+ PipResizeGestureHandler.this.onInputEvent(event);
+ finishInputEvent(event, true);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java
new file mode 100644
index 000000000000..efa5fc8bf8b1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.pip2.phone;
+
+/**
+ * A generic interface for a touch gesture.
+ */
+public abstract class PipTouchGesture {
+
+ /**
+ * Handle the touch down.
+ */
+ public void onDown(PipTouchState touchState) {}
+
+ /**
+ * Handle the touch move, and return whether the event was consumed.
+ */
+ public boolean onMove(PipTouchState touchState) {
+ return false;
+ }
+
+ /**
+ * Handle the touch up, and return whether the gesture was consumed.
+ */
+ public boolean onUp(PipTouchState touchState) {
+ return false;
+ }
+
+ /**
+ * Cleans up the high performance hint session if needed.
+ */
+ public void cleanUpHighPerfSessionMaybe() {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
new file mode 100644
index 000000000000..d093f1e5ccc1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2020 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.pip2.phone;
+
+import android.graphics.PointF;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.io.PrintWriter;
+
+/**
+ * This keeps track of the touch state throughout the current touch gesture.
+ */
+public class PipTouchState {
+ private static final String TAG = "PipTouchState";
+ private static final boolean DEBUG = false;
+
+ @VisibleForTesting
+ public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+ static final long HOVER_EXIT_TIMEOUT = 50;
+
+ private final ShellExecutor mMainExecutor;
+ private final ViewConfiguration mViewConfig;
+ private final Runnable mDoubleTapTimeoutCallback;
+ private final Runnable mHoverExitTimeoutCallback;
+
+ private VelocityTracker mVelocityTracker;
+ private long mDownTouchTime = 0;
+ private long mLastDownTouchTime = 0;
+ private long mUpTouchTime = 0;
+ private final PointF mDownTouch = new PointF();
+ private final PointF mDownDelta = new PointF();
+ private final PointF mLastTouch = new PointF();
+ private final PointF mLastDelta = new PointF();
+ private final PointF mVelocity = new PointF();
+ private boolean mAllowTouches = true;
+
+ // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing
+ private boolean mAllowInputEvents = true;
+ private boolean mIsUserInteracting = false;
+ // Set to true only if the multiple taps occur within the double tap timeout
+ private boolean mIsDoubleTap = false;
+ // Set to true only if a gesture
+ private boolean mIsWaitingForDoubleTap = false;
+ private boolean mIsDragging = false;
+ // The previous gesture was a drag
+ private boolean mPreviouslyDragging = false;
+ private boolean mStartedDragging = false;
+ private boolean mAllowDraggingOffscreen = false;
+ private int mActivePointerId;
+ private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
+
+ public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
+ Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
+ mViewConfig = viewConfig;
+ mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
+ mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * @return true if input processing is enabled for PiP in general.
+ */
+ public boolean getAllowInputEvents() {
+ return mAllowInputEvents;
+ }
+
+ /**
+ * @param allowInputEvents true to enable input processing for PiP in general.
+ */
+ public void setAllowInputEvents(boolean allowInputEvents) {
+ mAllowInputEvents = allowInputEvents;
+ }
+
+ /**
+ * Resets this state.
+ */
+ public void reset() {
+ mAllowDraggingOffscreen = false;
+ mIsDragging = false;
+ mStartedDragging = false;
+ mIsUserInteracting = false;
+ mLastTouchDisplayId = Display.INVALID_DISPLAY;
+ }
+
+ /**
+ * Processes a given touch event and updates the state.
+ */
+ public void onTouchEvent(MotionEvent ev) {
+ mLastTouchDisplayId = ev.getDisplayId();
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (!mAllowTouches) {
+ return;
+ }
+
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ addMovementToVelocityTracker(ev);
+
+ mActivePointerId = ev.getPointerId(0);
+ if (DEBUG) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Setting active pointer id on DOWN: %d", TAG, mActivePointerId);
+ }
+ mLastTouch.set(ev.getRawX(), ev.getRawY());
+ mDownTouch.set(mLastTouch);
+ mAllowDraggingOffscreen = true;
+ mIsUserInteracting = true;
+ mDownTouchTime = ev.getEventTime();
+ mIsDoubleTap = !mPreviouslyDragging
+ && (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+ mIsWaitingForDoubleTap = false;
+ mIsDragging = false;
+ mLastDownTouchTime = mDownTouchTime;
+ if (mDoubleTapTimeoutCallback != null) {
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on MOVE: %d", TAG, mActivePointerId);
+ break;
+ }
+
+ float x = ev.getRawX(pointerIndex);
+ float y = ev.getRawY(pointerIndex);
+ mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y);
+ mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y);
+
+ boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop();
+ if (!mIsDragging) {
+ if (hasMovedBeyondTap) {
+ mIsDragging = true;
+ mStartedDragging = true;
+ }
+ } else {
+ mStartedDragging = false;
+ }
+ mLastTouch.set(x, y);
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // Select a new active pointer id and reset the movement state
+ final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (DEBUG) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Relinquish active pointer id on POINTER_UP: %d",
+ TAG, mActivePointerId);
+ }
+ mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
+ // Update the velocity tracker
+ addMovementToVelocityTracker(ev);
+ mVelocityTracker.computeCurrentVelocity(1000,
+ mViewConfig.getScaledMaximumFlingVelocity());
+ mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Invalid active pointer id on UP: %d", TAG, mActivePointerId);
+ break;
+ }
+
+ mUpTouchTime = ev.getEventTime();
+ mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
+ mPreviouslyDragging = mIsDragging;
+ mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging
+ && (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+
+ }
+ // fall through to clean up
+ case MotionEvent.ACTION_CANCEL: {
+ recycleVelocityTracker();
+ break;
+ }
+ case MotionEvent.ACTION_BUTTON_PRESS: {
+ removeHoverExitTimeoutCallback();
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return the velocity of the active touch pointer at the point it is lifted off the screen.
+ */
+ public PointF getVelocity() {
+ return mVelocity;
+ }
+
+ /**
+ * @return the last touch position of the active pointer.
+ */
+ public PointF getLastTouchPosition() {
+ return mLastTouch;
+ }
+
+ /**
+ * @return the movement delta between the last handled touch event and the previous touch
+ * position.
+ */
+ public PointF getLastTouchDelta() {
+ return mLastDelta;
+ }
+
+ /**
+ * @return the down touch position.
+ */
+ public PointF getDownTouchPosition() {
+ return mDownTouch;
+ }
+
+ /**
+ * @return the movement delta between the last handled touch event and the down touch
+ * position.
+ */
+ public PointF getDownTouchDelta() {
+ return mDownDelta;
+ }
+
+ /**
+ * @return whether the user has started dragging.
+ */
+ public boolean isDragging() {
+ return mIsDragging;
+ }
+
+ /**
+ * @return whether the user is currently interacting with the PiP.
+ */
+ public boolean isUserInteracting() {
+ return mIsUserInteracting;
+ }
+
+ /**
+ * @return whether the user has started dragging just in the last handled touch event.
+ */
+ public boolean startedDragging() {
+ return mStartedDragging;
+ }
+
+ /**
+ * @return Display ID of the last touch event.
+ */
+ public int getLastTouchDisplayId() {
+ return mLastTouchDisplayId;
+ }
+
+ /**
+ * Sets whether touching is currently allowed.
+ */
+ public void setAllowTouches(boolean allowTouches) {
+ mAllowTouches = allowTouches;
+
+ // If the user happens to touch down before this is sent from the system during a transition
+ // then block any additional handling by resetting the state now
+ if (mIsUserInteracting) {
+ reset();
+ }
+ }
+
+ /**
+ * Disallows dragging offscreen for the duration of the current gesture.
+ */
+ public void setDisallowDraggingOffscreen() {
+ mAllowDraggingOffscreen = false;
+ }
+
+ /**
+ * @return whether dragging offscreen is allowed during this gesture.
+ */
+ public boolean allowDraggingOffscreen() {
+ return mAllowDraggingOffscreen;
+ }
+
+ /**
+ * @return whether this gesture is a double-tap.
+ */
+ public boolean isDoubleTap() {
+ return mIsDoubleTap;
+ }
+
+ /**
+ * @return whether this gesture will potentially lead to a following double-tap.
+ */
+ public boolean isWaitingForDoubleTap() {
+ return mIsWaitingForDoubleTap;
+ }
+
+ /**
+ * Schedules the callback to run if the next double tap does not occur. Only runs if
+ * isWaitingForDoubleTap() is true.
+ */
+ public void scheduleDoubleTapTimeoutCallback() {
+ if (mIsWaitingForDoubleTap) {
+ long delay = getDoubleTapTimeoutCallbackDelay();
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
+ }
+ }
+
+ long getDoubleTapTimeoutCallbackDelay() {
+ if (mIsWaitingForDoubleTap) {
+ return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the timeout callback if it's in queue.
+ */
+ public void removeDoubleTapTimeoutCallback() {
+ mIsWaitingForDoubleTap = false;
+ mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+ }
+
+ void scheduleHoverExitTimeoutCallback() {
+ mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+ mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
+ }
+
+ void removeHoverExitTimeoutCallback() {
+ mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+ }
+
+ void addMovementToVelocityTracker(MotionEvent event) {
+ if (mVelocityTracker == null) {
+ return;
+ }
+
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ private void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Dumps the {@link PipTouchState}.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
+ pw.println(innerPrefix + "mAllowInputEvents=" + mAllowInputEvents);
+ pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId);
+ pw.println(innerPrefix + "mLastTouchDisplayId=" + mLastTouchDisplayId);
+ pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
+ pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
+ pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
+ pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
+ pw.println(innerPrefix + "mVelocity=" + mVelocity);
+ pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
+ pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
+ pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
+ pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index b3eb2bfd9e9d..f5a8655b81f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -38,8 +38,6 @@
<!-- Increase trace size: 20mb for WM and 80mb for SF -->
<option name="run-command" value="cmd window tracing size 20480"/>
<option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
- <!-- uninstall Maps, so that latest version can be installed from pStash directly -->
- <option name="run-command" value="su root pm uninstall -k --user 0 com.google.android.apps.maps"/>
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="test-user-token" value="%TEST_USER%"/>
@@ -71,7 +69,6 @@
<option name="install-arg" value="-g"/>
<option name="install-arg" value="-r"/>
<option name="test-file-name" value="pstash://com.netflix.mediaclient"/>
- <option name="test-file-name" value="pstash://com.google.android.apps.maps"/>
</target_preparer>
<!-- Enable mocking GPS location by the test app -->
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index d7b5914130ee..9d4b426a6759 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -304,7 +304,7 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
_ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
const int32_t error = ExtractEntryToFile(mHandle, &(zipEntry->entry), fd);
if (error) {
- ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error));
+ ALOGW("ExtractToFile failed with %s", ErrorCodeString(error));
return false;
}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index 4dafd0aa6df4..42547832133b 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,7 +27,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
- "animationlib",
+ "//frameworks/libs/systemui:animationlib",
"frameworks-base-testutils",
"junit",
"kotlinx_coroutines_test",
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c1be6b5473d4..50fdb8aaf3cd 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -121,3 +121,13 @@ flag {
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
bug: "185136506"
}
+
+flag {
+ name: "enable_prevention_of_manager_scans_when_no_apps_scan"
+ namespace: "media_solutions"
+ description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning."
+ bug: "319604673"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7ed67dcde913..2a0648d87c85 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -23,9 +23,6 @@ import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.ActivityOptions.LaunchCookie;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
-import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -69,18 +66,6 @@ public final class MediaProjectionManager {
private static final String TAG = "MediaProjectionManager";
/**
- * This change id ensures that users are presented with a choice of capturing a single app
- * or the entire screen when initiating a MediaProjection session, overriding the usage of
- * MediaProjectionConfig#createConfigForDefaultDisplay.
- *
- * @hide
- */
- @ChangeId
- @Overridable
- @Disabled
- public static final long OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L;
-
- /**
* Intent extra to customize the permission dialog based on the host app's preferences.
* @hide
*/
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 7093ba42f40d..f86eb61c365f 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -139,7 +139,7 @@ public class RescueParty {
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
@VisibleForTesting
- static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
+ static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
private static final String NAME = "rescue-party-observer";
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index d21077ee7c5a..a30956ecf5a5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -98,7 +98,8 @@ fun runBiometricFlow(
context: Context,
openMoreOptionsPage: () -> Unit,
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
- onCancelFlowAndFinish: (String) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
getRequestDisplayInfo: RequestDisplayInfo? = null,
getProviderInfoList: List<ProviderInfo>? = null,
getProviderDisplayInfo: ProviderDisplayInfo? = null,
@@ -131,7 +132,7 @@ fun runBiometricFlow(
val callback: BiometricPrompt.AuthenticationCallback =
setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
- onCancelFlowAndFinish)
+ onCancelFlowAndFinish, onIllegalStateAndFinish)
val cancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
@@ -211,7 +212,8 @@ private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
private fun setupBiometricAuthenticationCallback(
sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
selectedEntry: EntryInfo,
- onCancelFlowAndFinish: (String) -> Unit
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
): BiometricPrompt.AuthenticationCallback {
val callback: BiometricPrompt.AuthenticationCallback =
object : BiometricPrompt.AuthenticationCallback() {
@@ -224,14 +226,12 @@ private fun setupBiometricAuthenticationCallback(
if (authResult != null) {
sendDataToProvider(selectedEntry, authResult)
} else {
- onCancelFlowAndFinish("The biometric flow succeeded but unexpectedly " +
+ onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
"returned a null value.")
- // TODO(b/326243754) : Propagate to provider
}
} catch (e: Exception) {
- onCancelFlowAndFinish("The biometric flow succeeded but failed on handling " +
+ onIllegalStateAndFinish("The biometric flow succeeded but failed on handling " +
"the result. See: \n$e\n")
- // TODO(b/326243754) : Propagate to provider
}
}
@@ -245,6 +245,12 @@ private fun setupBiometricAuthenticationCallback(
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "Authentication error-ed out: $errorCode and $errString")
+ if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) {
+ // Note that because the biometric prompt is imbued directly
+ // into the selector, parity applies to the selector's cancellation instead
+ // of the provider's biometric prompt cancellation.
+ onCancelFlowAndFinish()
+ }
// TODO(b/326243754) : Propagate to provider
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index b59ccc264630..4d7272c7716e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -148,7 +148,8 @@ fun GetCredentialScreen(
// activeEntry will always be what represents the single tap flow
biometricEntry = getCredentialUiState.activeEntry,
onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
- onCancelFlowAndFinish = viewModel::onIllegalUiState,
+ onCancelFlowAndFinish = viewModel::onUserCancel,
+ onIllegalStateAndFinish = viewModel::onIllegalUiState,
requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
providerInfoList = getCredentialUiState.providerInfoList,
providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
@@ -212,7 +213,8 @@ fun GetCredentialScreen(
@Composable
internal fun BiometricSelectionPage(
biometricEntry: EntryInfo?,
- onCancelFlowAndFinish: (String) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalStateAndFinish: (String) -> Unit,
onMoreOptionSelected: () -> Unit,
requestDisplayInfo: RequestDisplayInfo,
providerInfoList: List<ProviderInfo>,
@@ -230,6 +232,7 @@ internal fun BiometricSelectionPage(
openMoreOptionsPage = onMoreOptionSelected,
sendDataToProvider = onBiometricEntrySelected,
onCancelFlowAndFinish = onCancelFlowAndFinish,
+ onIllegalStateAndFinish = onIllegalStateAndFinish,
getRequestDisplayInfo = requestDisplayInfo,
getProviderInfoList = providerInfoList,
getProviderDisplayInfo = providerDisplayInfo,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 66fad3689eac..d6cbf2a0f581 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -22,7 +22,7 @@ android_library {
"guava",
"WifiTrackerLibRes",
- "iconloader",
+ "//frameworks/libs/systemui:iconloader",
"setupdesign",
"SettingsLibActionBarShadow",
@@ -88,6 +88,7 @@ java_defaults {
aconfig_declarations {
name: "settingslib_media_flags",
package: "com.android.settingslib.media.flags",
+ container: "system",
srcs: [
"aconfig/settingslib_media_flag_declarations.aconfig",
],
@@ -101,6 +102,7 @@ java_aconfig_library {
aconfig_declarations {
name: "settingslib_flags",
package: "com.android.settingslib.flags",
+ container: "system",
srcs: [
"aconfig/settingslib.aconfig",
],
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 54c5a14702f6..e09ab0086451 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -1,4 +1,5 @@
package: "com.android.settingslib.flags"
+container: "system"
flag {
name: "new_status_bar_icons"
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index f3e537b33230..4d70aec9fa5c 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -1,4 +1,5 @@
package: "com.android.settingslib.media.flags"
+container: "system"
flag {
name: "use_media_router2_for_info_media_manager"
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index 6b833cc68b93..0282f03e0b98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -16,6 +16,7 @@
package com.android.settingslib.applications;
+import android.annotation.NonNull;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -25,6 +26,7 @@ import android.os.UserHandle;
import androidx.annotation.VisibleForTesting;
import java.io.IOException;
+import java.util.UUID;
/**
* StorageStatsSource wraps the StorageStatsManager for testability purposes.
@@ -59,6 +61,10 @@ public class StorageStatsSource {
return mStorageStatsManager.getCacheQuotaBytes(volumeUuid, uid);
}
+ public long getTotalBytes(@NonNull UUID storageUuid) throws IOException {
+ return mStorageStatsManager.getTotalBytes(storageUuid);
+ }
+
/**
* Static class that provides methods for querying the amount of external storage available as
* well as breaking it up into several media types.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e34c50eb5ce6..4e6d3cbb60cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -174,6 +174,7 @@ public abstract class InfoMediaManager {
public void startScan() {
mMediaDevices.clear();
+ registerRouter();
startScanOnRouter();
updateRouteListingPreference();
refreshDevices();
@@ -188,10 +189,19 @@ public abstract class InfoMediaManager {
}
}
- public abstract void stopScan();
+ public final void stopScan() {
+ stopScanOnRouter();
+ unregisterRouter();
+ }
+
+ protected abstract void stopScanOnRouter();
protected abstract void startScanOnRouter();
+ protected abstract void registerRouter();
+
+ protected abstract void unregisterRouter();
+
protected abstract void transferToRoute(@NonNull MediaRoute2Info route);
protected abstract void selectRoute(
@@ -514,17 +524,20 @@ public abstract class InfoMediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
- for (MediaRoute2Info route : getAvailableRoutes()) {
+ RoutingSessionInfo activeSession = getActiveRoutingSession();
+
+ for (MediaRoute2Info route : getAvailableRoutes(activeSession)) {
if (DEBUG) {
Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ route.getVolume() + ", type : " + route.getType());
}
- addMediaDevice(route);
+ addMediaDevice(route, activeSession);
}
}
- private synchronized List<MediaRoute2Info> getAvailableRoutes() {
+
+ private synchronized List<MediaRoute2Info> getAvailableRoutes(
+ RoutingSessionInfo activeSession) {
List<MediaRoute2Info> availableRoutes = new ArrayList<>();
- RoutingSessionInfo activeSession = getActiveRoutingSession();
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(activeSession);
availableRoutes.addAll(selectedRoutes);
@@ -562,7 +575,7 @@ public abstract class InfoMediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
@VisibleForTesting
- void addMediaDevice(MediaRoute2Info route) {
+ void addMediaDevice(MediaRoute2Info route, RoutingSessionInfo activeSession) {
final int deviceType = route.getType();
MediaDevice mediaDevice = null;
switch (deviceType) {
@@ -627,14 +640,13 @@ public abstract class InfoMediaManager {
break;
}
- if (mediaDevice != null
- && getActiveRoutingSession().getSelectedRoutes().contains(route.getId())) {
- mediaDevice.setState(STATE_SELECTED);
- if (mCurrentConnectedDevice == null) {
- mCurrentConnectedDevice = mediaDevice;
- }
- }
if (mediaDevice != null) {
+ if (activeSession.getSelectedRoutes().contains(route.getId())) {
+ mediaDevice.setState(STATE_SELECTED);
+ if (mCurrentConnectedDevice == null) {
+ mCurrentConnectedDevice = mediaDevice;
+ }
+ }
mMediaDevices.add(mediaDevice);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index c4fac358c01a..23063da747af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -62,22 +62,30 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
@Override
protected void startScanOnRouter() {
if (!mIsScanning) {
- mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
mRouterManager.registerScanRequest();
mIsScanning = true;
}
}
@Override
- public void stopScan() {
+ protected void registerRouter() {
+ mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
if (mIsScanning) {
- mRouterManager.unregisterCallback(mMediaRouterCallback);
mRouterManager.unregisterScanRequest();
mIsScanning = false;
}
}
@Override
+ protected void unregisterRouter() {
+ mRouterManager.unregisterCallback(mMediaRouterCallback);
+ }
+
+ @Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
// TODO: b/279555229 - provide real user handle of a caller.
mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 2b8c2dd0d0e3..cf11c6da737f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -63,12 +63,22 @@ import java.util.List;
}
@Override
- public void stopScan() {
+ protected void startScanOnRouter() {
// Do nothing.
}
@Override
- protected void startScanOnRouter() {
+ protected void registerRouter() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void unregisterRouter() {
// Do nothing.
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 9c82cb1ef57d..0dceebab13f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -97,11 +97,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
@Override
protected void startScanOnRouter() {
- mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
- mRouter.registerRouteListingPreferenceUpdatedCallback(
- mExecutor, mRouteListingPreferenceCallback);
- mRouter.registerTransferCallback(mExecutor, mTransferCallback);
- mRouter.registerControllerCallback(mExecutor, mControllerCallback);
if (Flags.enableScreenOffScanning()) {
MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build();
mScanToken.compareAndSet(null, mRouter.requestScan(request));
@@ -111,7 +106,16 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
}
@Override
- public void stopScan() {
+ protected void registerRouter() {
+ mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
+ mRouter.registerRouteListingPreferenceUpdatedCallback(
+ mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerTransferCallback(mExecutor, mTransferCallback);
+ mRouter.registerControllerCallback(mExecutor, mControllerCallback);
+ }
+
+ @Override
+ protected void stopScanOnRouter() {
if (Flags.enableScreenOffScanning()) {
MediaRouter2.ScanToken token = mScanToken.getAndSet(null);
if (token != null) {
@@ -120,6 +124,10 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
} else {
mRouter.stopScan();
}
+ }
+
+ @Override
+ protected void unregisterRouter() {
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index d85d2534f856..f8dcec7315b2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -86,6 +86,30 @@ public class InfoMediaManagerTest {
private static final String TEST_DUPLICATED_ID_2 = "test_duplicated_id_2";
private static final String TEST_DUPLICATED_ID_3 = "test_duplicated_id_3";
+ private static final String TEST_SYSTEM_ROUTE_ID = "TEST_SYSTEM_ROUTE_ID";
+ private static final String TEST_BLUETOOTH_ROUTE_ID = "TEST_BT_ROUTE_ID";
+
+ private static final RoutingSessionInfo TEST_SYSTEM_ROUTING_SESSION =
+ new RoutingSessionInfo.Builder("FAKE_SYSTEM_ROUTING_SESSION_ID", TEST_PACKAGE_NAME)
+ .addSelectedRoute(TEST_SYSTEM_ROUTE_ID)
+ .addTransferableRoute(TEST_BLUETOOTH_ROUTE_ID)
+ .setSystemSession(true)
+ .build();
+
+ private static final MediaRoute2Info TEST_SELECTED_SYSTEM_ROUTE =
+ new MediaRoute2Info.Builder(TEST_SYSTEM_ROUTE_ID, "SELECTED_SYSTEM_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .build();
+
+ private static final MediaRoute2Info TEST_BLUETOOTH_ROUTE =
+ new MediaRoute2Info.Builder(TEST_BLUETOOTH_ROUTE_ID, "BLUETOOTH_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .setType(TYPE_BLUETOOTH_A2DP)
+ .setAddress("00:00:00:00:00:00")
+ .build();
+
@Mock
private MediaRouter2Manager mRouterManager;
@Mock
@@ -795,19 +819,19 @@ public class InfoMediaManagerTest {
when(route2Info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
when(route2Info.getId()).thenReturn(TEST_ID);
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof InfoMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_USB_DEVICE);
when(route2Info.getId()).thenReturn(TEST_ID);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_WIRED_HEADSET);
when(route2Info.getId()).thenReturn(TEST_ID);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
@@ -818,12 +842,12 @@ public class InfoMediaManagerTest {
when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
.thenReturn(cachedDevice);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof BluetoothMediaDevice).isTrue();
when(route2Info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue();
}
@@ -841,26 +865,27 @@ public class InfoMediaManagerTest {
.thenReturn(null);
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION);
assertThat(mInfoMediaManager.mMediaDevices.size()).isEqualTo(0);
}
@Test
public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() {
- final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
mock(CachedBluetoothDeviceManager.class);
+
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
+ RoutingSessionInfo selectedBtSession =
+ new RoutingSessionInfo.Builder(TEST_SYSTEM_ROUTING_SESSION)
+ .clearSelectedRoutes()
+ .clearTransferableRoutes()
+ .addSelectedRoute(TEST_BLUETOOTH_ROUTE_ID)
+ .addTransferableRoute(TEST_SYSTEM_ROUTE_ID)
+ .build();
- when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
- when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
- when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
- when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
- when(route2Info.getId()).thenReturn(TEST_ID);
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME))
+ .thenReturn(List.of(selectedBtSession));
when(mLocalBluetoothManager.getCachedDeviceManager())
.thenReturn(cachedBluetoothDeviceManager);
when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
@@ -868,7 +893,7 @@ public class InfoMediaManagerTest {
mInfoMediaManager.mRouterManager = mRouterManager;
mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(route2Info);
+ mInfoMediaManager.addMediaDevice(TEST_BLUETOOTH_ROUTE, selectedBtSession);
MediaDevice device = mInfoMediaManager.mMediaDevices.get(0);
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index bf4f60d84e4d..e9c267284e91 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -32,6 +32,7 @@ android_library {
"unsupportedappusage",
],
static_libs: [
+ "aconfig_demo_flags_java_lib",
"device_config_service_flags_java",
"libaconfig_java_proto_lite",
"SettingsLibDeviceStateRotationLock",
@@ -87,6 +88,7 @@ android_test {
aconfig_declarations {
name: "device_config_service_flags",
package: "com.android.providers.settings",
+ container: "system",
srcs: [
"src/com/android/providers/settings/device_config_service.aconfig",
],
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4e4c22fadcfd..a28cfeb4dc80 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -60,16 +60,17 @@ import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -165,8 +166,8 @@ final class SettingsState {
private static final String STORAGE_MIGRATION_FLAG =
"core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1";
- private static final String STORAGE_MIGRATION_LOG =
- "/metadata/aconfig/flags/storage_migration.log";
+ private static final String STORAGE_MIGRATION_MARKER_FILE =
+ "/metadata/aconfig/storage_test_mission_1";
/**
* This tag is applied to all aconfig default value-loaded flags.
@@ -1467,16 +1468,29 @@ final class SettingsState {
}
}
- if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) {
- File file = new File(STORAGE_MIGRATION_LOG);
- if (!file.exists()) {
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) {
- final long timestamp = System.currentTimeMillis();
- String entry = String.format("%d | Log init", timestamp);
- writer.write(entry);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "failed to write storage migration file", e);
+ if (isConfigSettingsKey(mKey) && name != null
+ && name.equals(STORAGE_MIGRATION_FLAG)) {
+ if (value.equals("true")) {
+ Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
+ if (!Files.exists(path)) {
+ Files.createFile(path);
+ }
+
+ Set<PosixFilePermission> perms =
+ Files.readAttributes(path, PosixFileAttributes.class).permissions();
+ perms.add(PosixFilePermission.OWNER_WRITE);
+ perms.add(PosixFilePermission.OWNER_READ);
+ perms.add(PosixFilePermission.GROUP_READ);
+ perms.add(PosixFilePermission.OTHERS_READ);
+ try {
+ Files.setPosixFilePermissions(path, perms);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "failed to set permissions on migration marker", e);
+ }
+ } else {
+ java.nio.file.Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE);
+ if (Files.exists(path)) {
+ Files.delete(path);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index c572bdb57c6a..d20fbf591a25 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -1,4 +1,5 @@
package: "com.android.providers.settings"
+container: "system"
flag {
name: "support_overrides"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 58040716db3e..b94e224850aa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -896,7 +896,6 @@
<!-- Permissions required for CTS test - CtsVoiceInteractionTestCases -->
<uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" />
- <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" />
<uses-permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" />
<uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 089782c5e82a..40db52eec81b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@ filegroup {
"src-release/**/*.kt",
"src-release/**/*.java",
],
+ visibility: ["//visibility:private"],
}
filegroup {
@@ -65,6 +66,7 @@ filegroup {
"src-debug/**/*.kt",
"src-debug/**/*.java",
],
+ visibility: ["//visibility:private"],
}
//Create a library to expose SystemUI's resources to other modules.
@@ -105,6 +107,7 @@ android_library {
},
use_resource_processor: true,
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-res",
"WifiTrackerLib",
"WindowManager-Shell",
@@ -117,7 +120,7 @@ android_library {
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
@@ -145,7 +148,7 @@ android_library {
"device_state_flags_lib",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"monet",
@@ -156,7 +159,7 @@ android_library {
"lottie",
"LowLightDreamLib",
"TraceurCommon",
- "motion_tool_lib",
+ "//frameworks/libs/systemui:motion_tool_lib",
"notification_flags_lib",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
@@ -263,7 +266,7 @@ android_library {
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"flag-junit-base",
"platform-parametric-runner-lib",
"androidx.viewpager2_viewpager2",
@@ -293,7 +296,7 @@ android_library {
"kotlinx-coroutines-core",
"kotlinx_coroutines_test",
"kotlin-reflect",
- "iconloader_base",
+ "//frameworks/libs/systemui:iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"metrics-helper-lib",
@@ -307,7 +310,7 @@ android_library {
"jsr330",
"WindowManager-Shell",
"LowLightDreamLib",
- "motion_tool_lib",
+ "//frameworks/libs/systemui:motion_tool_lib",
"androidx.core_core-animation-testing",
"androidx.compose.ui_ui",
"flag-junit",
@@ -344,6 +347,7 @@ android_library {
"compose/facade/enabled/src/**/*.kt",
],
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-tests-base",
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
@@ -398,6 +402,7 @@ android_app {
"compose/facade/enabled/src/**/*.kt",
],
static_libs: [
+ "//frameworks/libs/systemui:compilelib",
"SystemUI-tests-base",
"androidx.compose.runtime_runtime",
],
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index af89f63cb4e3..4bfc6296b8a3 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,13 @@ flag {
}
flag {
+ name: "notification_heads_up_cycling"
+ namespace: "systemui"
+ description: "Heads-up notification cycling animation for the Notification Avalanche feature."
+ bug: "316404716"
+}
+
+flag {
name: "notification_minimalism_prototype"
namespace: "systemui"
description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 1ce3be897259..dec664fa7a14 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -47,8 +47,8 @@ android_library {
"com_android_systemui_flags_lib",
"SystemUIShaderLib",
"WindowManager-Shell-shared",
- "animationlib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:animationlib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 1c938a6c19a5..51a7e8ed8b40 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -34,7 +34,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.contains
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
-import com.android.systemui.customization.R as customizationR
import com.android.systemui.customization.R
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
@@ -65,32 +64,24 @@ constructor(
return
}
val context = LocalContext.current
- MovableElement(key = smallClockElementKey, modifier = modifier) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
- }
- },
- update = {
- it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
- },
- modifier =
- Modifier.height(dimensionResource(R.dimen.small_clock_height))
- .padding(
- horizontal =
- dimensionResource(customizationR.dimen.clock_padding_start)
- )
- .padding(top = { viewModel.getSmallClockTopMargin(context) })
- .onTopPlacementChanged(onTopChanged)
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ ensureClockViewExists(checkNotNull(currentClock).smallClock.view)
+ }
+ },
+ update = { it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) },
+ modifier =
+ modifier
+ .height(dimensionResource(R.dimen.small_clock_height))
+ .padding(top = { viewModel.getSmallClockTopMargin(context) })
+ .onTopPlacementChanged(onTopChanged)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ )
+ .element(smallClockElementKey),
+ )
}
@Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 9dbeeda42986..1120914fec7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,15 +47,16 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
@@ -832,7 +833,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Gone, "reason")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index 16ec9aa897fb..f9f7df820217 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -122,7 +122,6 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
bouncerInteractor.authenticate(WRONG_PIN)
}
- assertThat(message?.isUpdateAnimated).isFalse()
val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
advanceTimeBy(lockoutEndMs - testScope.currentTime)
@@ -133,6 +132,7 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
fun lockoutMessage() =
testScope.runTest {
val message by collectLastValue(underTest.message)
+ val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
runCurrent()
@@ -140,14 +140,14 @@ class BouncerMessageViewModelTest : SysuiTestCase() {
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
bouncerInteractor.authenticate(WRONG_PIN)
runCurrent()
- if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ if (times == FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+ assertTryAgainMessage(message?.text, lockoutSeconds)
+ assertThat(message?.isUpdateAnimated).isFalse()
+ } else {
assertThat(message?.text).isEqualTo("Wrong PIN. Try again.")
assertThat(message?.isUpdateAnimated).isTrue()
}
}
- val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
- assertTryAgainMessage(message?.text, lockoutSeconds)
- assertThat(message?.isUpdateAnimated).isFalse()
repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time ->
advanceTimeBy(1.seconds)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 71c578545647..c28cf348b9fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.inputmethod.data.model.InputMethodModel
import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
@@ -153,7 +152,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
// No input entered.
@@ -329,7 +327,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 51b73ee92df5..14d36343041d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -373,7 +372,6 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 564795429fa6..a0db482cc360 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -28,7 +28,6 @@ import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -387,7 +386,6 @@ class PinBouncerViewModelTest : SysuiTestCase() {
private fun TestScope.lockDeviceAndOpenPinBouncer() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(Scenes.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
index 6bff0dc7bd9e..5e120b5f9560 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -16,11 +16,15 @@
package com.android.systemui.communal.data.repository
+import android.content.Context
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
@@ -34,16 +38,22 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.FakeSharedPreferences
import com.google.common.truth.Truth.assertThat
import java.io.File
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -69,20 +79,12 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
USER_INFOS[1].id to FakeSharedPreferences()
)
)
- underTest =
- CommunalPrefsRepositoryImpl(
- testScope.backgroundScope,
- kosmos.testDispatcher,
- userRepository,
- userFileManager,
- logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
- tableLogBuffer,
- )
}
@Test
fun isCtaDismissedValue_byDefault_isFalse() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
assertThat(isCtaDismissed).isFalse()
}
@@ -90,6 +92,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Test
fun isCtaDismissedValue_onSet_isTrue() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
underTest.setCtaDismissedForCurrentUser()
@@ -99,6 +102,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
@Test
fun isCtaDismissedValue_whenSwitchUser() =
testScope.runTest {
+ underTest = createCommunalPrefsRepositoryImpl(userFileManager)
val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
underTest.setCtaDismissedForCurrentUser()
@@ -118,6 +122,44 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
assertThat(isCtaDismissed).isTrue()
}
+ @Test
+ fun getSharedPreferences_whenFileRestored() =
+ testScope.runTest {
+ val userFileManagerSpy = Mockito.spy(userFileManager)
+ underTest = createCommunalPrefsRepositoryImpl(userFileManagerSpy)
+
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+ assertThat(isCtaDismissed).isFalse()
+ clearInvocations(userFileManagerSpy)
+
+ // Received restore finished event.
+ kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(BackupHelper.ACTION_RESTORE_FINISHED),
+ )
+ runCurrent()
+
+ // Get shared preferences from the restored file.
+ verify(userFileManagerSpy, atLeastOnce())
+ .getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userRepository.getSelectedUserInfo().id
+ )
+ }
+
+ private fun createCommunalPrefsRepositoryImpl(userFileManager: UserFileManager) =
+ CommunalPrefsRepositoryImpl(
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ userRepository,
+ userFileManager,
+ kosmos.broadcastDispatcher,
+ logcatLogBuffer("CommunalPrefsRepositoryImplTest"),
+ tableLogBuffer,
+ )
+
private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
UserFileManager {
override fun getFile(fileName: String, userId: Int): File {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 9536084fdb23..73373d553560 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -5,15 +5,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -25,7 +21,6 @@ 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.verify
import org.mockito.MockitoAnnotations
@@ -37,12 +32,10 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
- @Mock private lateinit var keyguardStateController: KeyguardStateController
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val userRepository = FakeUserRepository()
- private val keyguardRepository = FakeKeyguardRepository()
private lateinit var underTest: DeviceEntryRepository
@@ -59,35 +52,9 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
- keyguardStateController = keyguardStateController,
- keyguardRepository = keyguardRepository,
)
testScope.runCurrent()
}
-
- @Test
- fun isUnlocked() =
- testScope.runTest {
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isUnlocked by collectLastValue(underTest.isUnlocked)
-
- runCurrent()
- assertThat(isUnlocked).isFalse()
-
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController, Mockito.atLeastOnce()).addCallback(captor.capture())
-
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
- captor.value.onUnlockedChanged()
- runCurrent()
- assertThat(isUnlocked).isTrue()
-
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- runCurrent()
- assertThat(isUnlocked).isFalse()
- }
-
@Test
fun isLockscreenEnabled() =
testScope.runTest {
@@ -102,17 +69,6 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
}
@Test
- fun reportSuccessfulAuthentication_updatesIsUnlocked() =
- testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isUnlocked)
- assertThat(isUnlocked).isFalse()
-
- underTest.reportSuccessfulAuthentication()
-
- assertThat(isUnlocked).isTrue()
- }
-
- @Test
fun isBypassEnabled_disabledInController() =
testScope.runTest {
whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 70ceb2a75d7c..5caf35ba90d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -41,8 +41,10 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -89,22 +91,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
- kosmos.fakeDeviceEntryRepository.apply {
- setLockscreenEnabled(false)
-
- // Toggle isUnlocked, twice.
- //
- // This is done because the underTest.isUnlocked flow doesn't receive values from
- // just changing the state above; the actual isUnlocked state needs to change to
- // cause the logic under test to "pick up" the current state again.
- //
- // It is done twice to make sure that we don't actually change the isUnlocked state
- // from what it originally was.
- setUnlocked(!isUnlocked.value)
- runCurrent()
- setUnlocked(!isUnlocked.value)
- runCurrent()
- }
+ kosmos.fakeDeviceEntryRepository.apply { setLockscreenEnabled(false) }
+ runCurrent()
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isTrue()
@@ -125,7 +113,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Sim
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
@@ -271,7 +261,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -283,7 +272,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
@@ -295,7 +283,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -307,7 +297,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
@@ -333,7 +325,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
underTest.attemptDeviceEntry()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 51db4513bb39..a7a7bea313fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,16 +25,30 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class DeviceUnlockedInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -46,71 +61,170 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
applicationScope = testScope.backgroundScope,
authenticationInteractor = kosmos.authenticationInteractor,
deviceEntryRepository = deviceEntryRepository,
+ trustInteractor = kosmos.trustInteractor,
+ faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
+ powerInteractor = kosmos.powerInteractor,
)
+ @Before
+ fun setup() {
+ kosmos.fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
+ }
+
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsNone_isTrue() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsNone_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- assertThat(isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull()
}
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsPin_isTrue() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPin_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-
- assertThat(isUnlocked).isTrue()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.Fingerprint)
}
@Test
- fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsSim_isFalse() =
+ fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(true)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsNone_isTrue() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsNone_isTrue() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- assertThat(isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsPin_isFalse() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsPin_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
@Test
- fun isDeviceUnlocked_whenLockedAndAuthMethodIsSim_isFalse() =
+ fun deviceUnlockStatus_whenLockedAndAuthMethodIsSim_isFalse() =
testScope.runTest {
- val isUnlocked by collectLastValue(underTest.isDeviceUnlocked)
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- deviceEntryRepository.setUnlocked(false)
authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
- assertThat(isUnlocked).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+
+ @Test
+ fun deviceUnlockStatus_whenFaceIsAuthenticatedWhileAwakeWithBypass_isTrue() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.powerInteractor.setAwakeForTest()
+
+ kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.FaceWithBypass)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenFaceIsAuthenticatedWithoutBypass_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.FaceWithoutBypass)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenFingerprintIsAuthenticated_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.Fingerprint)
+ }
+
+ @Test
+ fun deviceUnlockStatus_whenUnlockedByTrustAgent_providesThatInfo() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.fakeUserRepository.setSelectedUserInfo(
+ primaryUser,
+ SelectionStatus.SELECTION_COMPLETE
+ )
+
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(deviceUnlockStatus?.deviceUnlockSource)
+ .isEqualTo(DeviceUnlockSource.TrustAgent)
+ }
+
+ @Test
+ fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
+ companion object {
+ private const val primaryUserId = 1
+ private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
+
+ private val secondaryUser = UserInfo(2, "secondary user", 0)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 6a86801cba90..0f8fc3824e3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -63,7 +63,6 @@ import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.Optional;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@@ -119,6 +118,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
private static final float TOUCH_REGION = .3f;
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -142,6 +142,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
mUiEventLogger);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -160,9 +161,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
*/
@Test
public void testSessionStart() {
- mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion);
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
- verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION));
+ verify(mRegion).union(mRectCaptor.capture());
final Rect bounds = mRectCaptor.getValue();
final Rect expected = new Rect();
@@ -194,6 +195,85 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
UP,
}
+ @Test
+ public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+ new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
+ verify(mRegion).union(mRectCaptor.capture());
+ final Rect bounds = mRectCaptor.getValue();
+
+ final Rect expected = new Rect();
+ final float minBouncerHeight =
+ SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT;
+ final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight);
+
+ expected.set(0, minAllowableBottom , SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ @Test
+ public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+ new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
+ verify(mRegion).union(mRectCaptor.capture());
+ final Rect bounds = mRectCaptor.getValue();
+
+ final Rect expected = new Rect();
+ final int gestureAreaTop = SCREEN_HEIGHT_PX - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+ expected.set(0, gestureAreaTop, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ @Test
+ public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
+ final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
+ - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+ final int minimumSwipeAreaTop = SCREEN_HEIGHT_PX
+ - Math.round(SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT);
+
+ Rect exclusionRect = new Rect(0, 0, SCREEN_WIDTH_PX,
+ (normalSwipeAreaTop + minimumSwipeAreaTop) / 2);
+
+ mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, exclusionRect);
+
+ verify(mRegion).union(mRectCaptor.capture());
+
+ final Rect bounds = mRectCaptor.getValue();
+ final Rect expected = new Rect();
+
+ final int expectedSwipeAreaBottom = exclusionRect.bottom;
+ expected.set(0, expectedSwipeAreaBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+ assertThat(bounds).isEqualTo(expected);
+
+ onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+ }
+
+ private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
+ DreamTouchHandler.TouchSession touchSession,
+ NotificationShadeWindowController notificationShadeWindowController) {
+ touchHandler.onSessionStart(touchSession);
+ verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
+ ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+ verify(touchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(touchSession).registerInputListener(eventListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
/**
* Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
index 056a401a2644..0f5e45814608 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -37,14 +37,17 @@ package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -247,14 +250,20 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
val occludingActivityWillDismissKeyguard by
collectLastValue(underTest.occludingActivityWillDismissKeyguard)
assertThat(occludingActivityWillDismissKeyguard).isFalse()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
// Unlock device:
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(occludingActivityWillDismissKeyguard).isTrue()
// Re-lock device:
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.powerInteractor.setAsleepForTest()
runCurrent()
assertThat(occludingActivityWillDismissKeyguard).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 2fd2ef1f3240..66f7e015a133 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -121,7 +121,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
AuthenticationMethodModel.Pin
}
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
kosmos.shadeRepository.setShadeMode(
if (isSingleShade) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt
new file mode 100644
index 000000000000..d0cd56fce778
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.interactor
+
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val mockSensorPrivacyController =
+ mock<IndividualSensorPrivacyController> {
+ whenever(isSensorBlocked(eq(CAMERA))).thenReturn(false) // determines initial value
+ }
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ SensorPrivacyToggleTileDataInteractor(
+ testScope.testScheduler,
+ mockSensorPrivacyController,
+ CAMERA
+ )
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ whenever(mockSensorPrivacyController.supportsSensorToggle(eq(CAMERA))).thenReturn(true)
+
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+ runCurrent()
+
+ assertThat(availability).hasSize(1)
+ assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesPrivacyControllerIsSensorBlocked() =
+ testScope.runTest {
+ val callbackCaptor = argumentCaptor<IndividualSensorPrivacyController.Callback>()
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+ verify(mockSensorPrivacyController).addCallback(callbackCaptor.capture())
+ val callback = callbackCaptor.value
+
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse()
+
+ callback.onSensorBlockedChanged(CAMERA, true)
+ runCurrent()
+ assertThat(data!!.isBlocked).isTrue()
+
+ callback.onSensorBlockedChanged(CAMERA, false)
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse()
+
+ callback.onSensorBlockedChanged(MICROPHONE, true)
+ runCurrent()
+ assertThat(data!!.isBlocked).isFalse() // We're NOT listening for MIC sensor changes
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..562e6ebcc029
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.interactor
+
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ // The keyguard repository below is the same one kosmos used to create the interactor above
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val mockActivityStarter = kosmos.activityStarter
+ private val mockSensorPrivacyController = mock<IndividualSensorPrivacyController>()
+ private val fakeSafetyCenterManager = mock<SafetyCenterManager>()
+
+ private val underTest =
+ SensorPrivacyToggleTileUserActionInteractor(
+ inputHandler,
+ keyguardInteractor,
+ mockActivityStarter,
+ mockSensorPrivacyController,
+ fakeSafetyCenterManager,
+ CAMERA
+ )
+
+ @Test
+ fun handleClickWhenNotBlocked() = runTest {
+ val originalIsBlocked = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ }
+
+ @Test
+ fun handleClickWhenBlocked() = runTest {
+ val originalIsBlocked = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ }
+
+ @Test
+ fun handleClick_whenKeyguardIsDismissableAndShowing_whenControllerRequiresAuth() = runTest {
+ whenever(mockSensorPrivacyController.requiresAuthentication()).thenReturn(true)
+ keyguardRepository.setKeyguardDismissible(true)
+ keyguardRepository.setKeyguardShowing(true)
+ val originalIsBlocked = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked))
+ )
+
+ verify(mockSensorPrivacyController, never())
+ .setSensorBlocked(
+ eq(SensorPrivacyManager.Sources.QS_TILE),
+ eq(CAMERA),
+ eq(!originalIsBlocked)
+ )
+ verify(mockActivityStarter).postQSRunnableDismissingKeyguard(any())
+ }
+
+ @Test
+ fun handleLongClick_whenSafetyManagerEnabled_privacyControlsIntent() = runTest {
+ whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ }
+ }
+
+ @Test
+ fun handleLongClick_whenSafetyManagerDisabled_privacySettingsIntent() = runTest {
+ whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleClick_microphone_flipsController() = runTest {
+ val micUserActionInteractor =
+ SensorPrivacyToggleTileUserActionInteractor(
+ inputHandler,
+ keyguardInteractor,
+ mockActivityStarter,
+ mockSensorPrivacyController,
+ fakeSafetyCenterManager,
+ MICROPHONE
+ )
+
+ micUserActionInteractor.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(false))
+ )
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(true))
+
+ micUserActionInteractor.handleInput(
+ QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(true))
+ )
+ verify(mockSensorPrivacyController)
+ .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(false))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
new file mode 100644
index 000000000000..5e7aadcda6db
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.qsMicrophoneSensorPrivacyToggleTileConfig
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.CameraPrivacyTileResources
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.MicrophonePrivacyTileResources
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val cameraConfig = kosmos.qsCameraSensorPrivacyToggleTileConfig
+ private val micConfig = kosmos.qsMicrophoneSensorPrivacyToggleTileConfig
+
+ @Test
+ fun mapCamera_notBlocked() {
+ val mapper = createMapper(CameraPrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(false)
+
+ val outputState = mapper.map(cameraConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_available),
+ R.drawable.qs_camera_access_icon_on,
+ null,
+ CAMERA
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapCamera_blocked() {
+ val mapper = createMapper(CameraPrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(true)
+
+ val outputState = mapper.map(cameraConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_blocked),
+ R.drawable.qs_camera_access_icon_off,
+ null,
+ CAMERA
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapMic_notBlocked() {
+ val mapper = createMapper(MicrophonePrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(false)
+
+ val outputState = mapper.map(micConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_available),
+ R.drawable.qs_mic_access_on,
+ null,
+ MICROPHONE
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun mapMic_blocked() {
+ val mapper = createMapper(MicrophonePrivacyTileResources)
+ val inputModel = SensorPrivacyToggleTileModel(true)
+
+ val outputState = mapper.map(micConfig, inputModel)
+
+ val expectedState =
+ createSensorPrivacyToggleTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_camera_mic_blocked),
+ R.drawable.qs_mic_access_off,
+ null,
+ MICROPHONE
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createMapper(
+ sensorResources: SensorPrivacyTileResources
+ ): SensorPrivacyToggleTileMapper {
+ val mapper =
+ SensorPrivacyToggleTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_camera_access_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_camera_access_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_mic_access_off, TestStubDrawable())
+ addOverride(R.drawable.qs_mic_access_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ sensorResources,
+ )
+ return mapper
+ }
+
+ private fun createSensorPrivacyToggleTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ sensorId: Int,
+ ): QSTileState {
+ val label =
+ if (sensorId == CAMERA) context.getString(R.string.quick_settings_camera_label)
+ else context.getString(R.string.quick_settings_mic_label)
+
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ stateDescription,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index fd685192fdab..98cbda2efd2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -50,6 +50,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -265,8 +266,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
val startable =
@@ -291,6 +290,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
)
startable.start()
@@ -371,8 +371,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+ val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
+
+ assertThat(canSwipeToEnter).isTrue()
assertCurrentScene(Scenes.Lockscreen)
// Emulate a user swipe to dismiss the lockscreen.
@@ -437,17 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
@Test
- fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
- testScope.runTest {
- unlockDevice()
- assertCurrentScene(Scenes.Gone)
- putDeviceToSleep(instantlyLockDevice = false)
- assertCurrentScene(Scenes.Lockscreen)
- wakeUpDevice()
- assertCurrentScene(Scenes.Gone)
- }
-
- @Test
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
@@ -540,7 +532,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fakeSceneDataSource.pause()
introduceLockedSim()
emulatePendingTransitionProgress(expectedVisible = true)
- enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None)
+ enterSimPin(
+ authMethodAfterSimUnlock = AuthenticationMethodModel.None,
+ enableLockscreen = false
+ )
+
assertCurrentScene(Scenes.Gone)
}
@@ -708,7 +704,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(authMethod.isSecure)
.isTrue()
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
}
@@ -721,9 +716,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
emulateUserDrivenTransition(Scenes.Bouncer)
fakeSceneDataSource.pause()
enterPin()
- // This repository state is not changed by the AuthInteractor, it relies on
- // KeyguardStateController.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+
emulatePendingTransitionProgress(
expectedVisible = false,
)
@@ -763,7 +756,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
* Does not assert that the device is locked or unlocked.
*/
private fun TestScope.enterSimPin(
- authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None
+ authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None,
+ enableLockscreen: Boolean = true,
) {
assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
.that(getCurrentSceneInUi())
@@ -778,9 +772,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
pinBouncerViewModel.onPinButtonClicked(digit)
}
pinBouncerViewModel.onAuthenticateButtonClicked()
- setAuthMethod(authMethodAfterSimUnlock)
kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
+
+ setAuthMethod(authMethodAfterSimUnlock, enableLockscreen)
+ runCurrent()
}
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index f645f1cc4369..143c0f277209 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -23,7 +23,8 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
@@ -79,7 +80,9 @@ class SceneInteractorTest : SysuiTestCase() {
val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
underTest.changeScene(Scenes.Gone, "reason")
@@ -88,11 +91,7 @@ class SceneInteractorTest : SysuiTestCase() {
@Test(expected = IllegalStateException::class)
fun changeScene_toGoneWhenStillLocked_throws() =
- testScope.runTest {
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
- underTest.changeScene(Scenes.Gone, "reason")
- }
+ testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") }
@Test
fun sceneChanged_inDataSource() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index e330a359add5..3fd5306abb71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -39,9 +39,13 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -135,6 +139,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
)
}
@@ -145,6 +150,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val isVisible by collectLastValue(sceneInteractor.isVisible)
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -198,6 +204,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val isVisible by collectLastValue(sceneInteractor.isVisible)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Lockscreen,
isDeviceProvisioned = false,
@@ -249,14 +256,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
+ startsAwake = false,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
-
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
}
@@ -265,13 +272,16 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = false,
initialSceneKey = Scenes.Bouncer,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -281,13 +291,16 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isBypassEnabled = true,
initialSceneKey = Scenes.Lockscreen,
)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -305,7 +318,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
}
@@ -327,7 +339,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade)
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Shade)
@@ -346,7 +360,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -383,7 +396,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
.forEachIndexed { index, sceneKey ->
if (sceneKey == Scenes.Gone) {
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
}
fakeSceneDataSource.pause()
@@ -453,6 +468,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
powerInteractor.setAwakeForTest()
+ runCurrent()
assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
}
@@ -529,7 +545,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
underTest.start()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -564,7 +582,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
// Changing to the Gone scene should report a successful unlock.
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
runCurrent()
@@ -759,7 +779,9 @@ class SceneContainerStartableTest : SysuiTestCase() {
runCurrent()
verify(falsingCollector).onBouncerShown()
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
runCurrent()
@@ -830,6 +852,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene)
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -985,6 +1008,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
testScope.runTest {
val transitionStateFlow =
prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
isDeviceUnlocked = true,
initialSceneKey = Scenes.Gone,
)
@@ -1147,6 +1171,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
assert(isLockscreenEnabled) {
"Lockscreen cannot be disabled while having a secure authentication method"
}
+ if (isDeviceUnlocked) {
+ kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
}
check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) {
@@ -1154,8 +1183,13 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
sceneContainerFlags.enabled = true
- kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ authenticationMethod?.let {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
+ isLockscreenEnabled = isLockscreenEnabled
+ )
+ }
runCurrent()
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
@@ -1166,12 +1200,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlow.value = ObservableTransitionState.Idle(it)
sceneInteractor.changeScene(it, "reason")
}
- authenticationMethod?.let {
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
- isLockscreenEnabled = isLockscreenEnabled
- )
- }
if (startsAwake) {
powerInteractor.setAwakeForTest()
} else {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index cd79ed1a8965..cbbcce96873b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -21,10 +21,11 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
@@ -38,6 +39,7 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -53,7 +55,7 @@ import org.mockito.Mockito.verify
class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
@@ -68,7 +70,9 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
set(Flags.NSSL_DEBUG_LINES, false)
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
}
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
testScope.runCurrent()
shadeInteractor = kosmos.shadeInteractor
underTest = kosmos.shadeControllerSceneImpl
@@ -161,6 +165,10 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
testScope.runTest {
// GIVEN shade is collapsed and a post-collapse action is enqueued
val testRunnable = mock<Runnable>()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
setCollapsed()
underTest.postOnShadeExpanded(testRunnable)
@@ -179,7 +187,14 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
testScope.runCurrent()
}
- private fun setDeviceEntered(isEntered: Boolean) {
+ private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+ if (isEntered) {
+ // Unlock the device marking the device has entered.
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ }
setScene(
if (isEntered) {
Scenes.Gone
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index ad40f8eab4f6..6485c475eeb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -24,9 +24,12 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -49,7 +52,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
private val sceneInteractor = kosmos.sceneInteractor
private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
@@ -71,7 +73,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
fun legacyPanelExpansion_whenIdle_whenLocked() =
testScope.runTest {
underTest = kosmos.panelExpansionInteractorImpl
- setUnlocked(false)
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
@@ -95,7 +96,15 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
fun legacyPanelExpansion_whenIdle_whenUnlocked() =
testScope.runTest {
underTest = kosmos.panelExpansionInteractorImpl
- setUnlocked(true)
+ val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+
+ assertThat(unlockStatus)
+ .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint))
+
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
@@ -147,14 +156,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() {
assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse()
}
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
private fun TestScope.changeScene(
toScene: SceneKey,
assertDuringProgress: ((progress: Float) -> Unit) = {},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index d309c6bcfd58..e759b504d5c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -48,7 +47,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
val testScope = kosmos.testScope
val sceneInteractor = kosmos.sceneInteractor
- val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
val underTest = kosmos.shadeBackActionInteractor
@Before
@@ -78,7 +76,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
@Test
fun animateCollapseQs_fullyCollapse_locked() =
testScope.runTest {
- deviceEntryRepository.setUnlocked(false)
setScene(Scenes.QuickSettings)
underTest.animateCollapseQs(true)
runCurrent()
@@ -95,7 +92,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() {
}
private fun enterDevice() {
- deviceEntryRepository.setUnlocked(true)
testScope.runCurrent()
setScene(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index 52caa787bb2f..2ab934c2386e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -21,11 +21,15 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -40,6 +44,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -48,6 +53,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeStartableTest : SysuiTestCase() {
@@ -95,7 +101,14 @@ class ShadeStartableTest : SysuiTestCase() {
underTest.start()
- setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Gone)
@@ -120,14 +133,6 @@ class ShadeStartableTest : SysuiTestCase() {
}
}
- private fun TestScope.setUnlocked(isUnlocked: Boolean) {
- val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
- deviceEntryRepository.setUnlocked(isUnlocked)
- runCurrent()
-
- assertThat(isDeviceUnlocked).isEqualTo(isUnlocked)
- }
-
private fun TestScope.changeScene(
toScene: SceneKey,
transitionState: MutableStateFlow<ObservableTransitionState>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 1d6b22309579..77109d65fadc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -29,6 +29,8 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.qs.footerActionsController
@@ -140,7 +142,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
.isEqualTo(Scenes.Lockscreen)
@@ -153,7 +154,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
.isEqualTo(Scenes.Gone)
@@ -178,7 +181,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
testScope.runTest {
val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
@@ -196,7 +198,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
assertThat(isClickable).isFalse()
@@ -209,7 +213,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
assertThat(isClickable).isTrue()
@@ -222,7 +225,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 30564bb6eb84..29f286fd31fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -47,12 +48,14 @@ import org.mockito.junit.MockitoRule
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
class AvalancheControllerTest : SysuiTestCase() {
- private val mAvalancheController = AvalancheController()
-
// For creating mocks
@get:Rule var rule: MockitoRule = MockitoJUnit.rule()
@Mock private val runnableMock: Runnable? = null
+ // For creating AvalancheController
+ @Mock private lateinit var dumpManager: DumpManager
+ private lateinit var mAvalancheController: AvalancheController
+
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
@@ -73,7 +76,10 @@ class AvalancheControllerTest : SysuiTestCase() {
)
.then { i: InvocationOnMock -> i.getArgument(0) }
- // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
+ // declaration, where mocks are null
+ mAvalancheController = AvalancheController(dumpManager)
+
testableHeadsUpManager =
TestableHeadsUpManager(
mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 3dc449514699..7c130be7849e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -73,7 +74,9 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
- private AvalancheController mAvalancheController = new AvalancheController();
+
+ @Mock private DumpManager dumpManager;
+ private AvalancheController mAvalancheController;
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -130,6 +133,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
public void SysuiSetup() throws Exception {
super.SysuiSetup();
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+ mAvalancheController = new AvalancheController(dumpManager);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 61a79d897b0b..a8a75c052000 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -32,6 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -76,7 +77,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
- private AvalancheController mAvalancheController = new AvalancheController();
+ @Mock private DumpManager dumpManager;
+ private AvalancheController mAvalancheController;
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -154,6 +156,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
mDependency.injectMockDependency(NotificationShadeWindowController.class);
mContext.getOrCreateTestableResources().addOverride(
R.integer.ambient_notification_extension_time, 500);
+
+ mAvalancheController = new AvalancheController(dumpManager);
}
@Test
diff --git a/packages/SystemUI/res/anim/slide_in_up.xml b/packages/SystemUI/res/anim/slide_in_up.xml
new file mode 100644
index 000000000000..6089a2809de3
--- /dev/null
+++ b/packages/SystemUI/res/anim/slide_in_up.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="100%p"
+ android:toYDelta="0"
+ android:duration="@android:integer/config_shortAnimTime" />
diff --git a/packages/SystemUI/res/anim/slide_out_down.xml b/packages/SystemUI/res/anim/slide_out_down.xml
new file mode 100644
index 000000000000..5a7b591fce9e
--- /dev/null
+++ b/packages/SystemUI/res/anim/slide_out_down.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<translate
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromYDelta="0"
+ android:toYDelta="100%p"
+ android:duration="@android:integer/config_shortAnimTime" />
diff --git a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
index 1b12e74141a7..0406f0e4304e 100644
--- a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
+++ b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml
@@ -14,12 +14,15 @@
limitations under the License
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48"
- android:tint="?android:attr/textColorSecondary">
+ android:tint="?androidprv:attr/materialColorOnSurfaceVariant">
<path
android:fillColor="@android:color/white"
+ android:strokeColor="@android:color/white"
+ android:strokeWidth="2"
android:pathData="M39.8,41.95 L26.65,28.8Q25.15,30.1 23.15,30.825Q21.15,31.55 18.9,31.55Q13.5,31.55 9.75,27.8Q6,24.05 6,18.75Q6,13.45 9.75,9.7Q13.5,5.95 18.85,5.95Q24.15,5.95 27.875,9.7Q31.6,13.45 31.6,18.75Q31.6,20.9 30.9,22.9Q30.2,24.9 28.8,26.65L42,39.75ZM18.85,28.55Q22.9,28.55 25.75,25.675Q28.6,22.8 28.6,18.75Q28.6,14.7 25.75,11.825Q22.9,8.95 18.85,8.95Q14.75,8.95 11.875,11.825Q9,14.7 9,18.75Q9,22.8 11.875,25.675Q14.75,28.55 18.85,28.55Z"/>
-</vector> \ No newline at end of file
+</vector>
diff --git a/packages/SystemUI/res/drawable/shortcut_button_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
index bf908532ac17..2e2d9b9a3e35 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_colored.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="16dp"/>
+ <corners android:radius="@dimen/ksh_button_corner_radius"/>
<solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
index f692ed975319..5b88bb922a9e 100644
--- a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
+++ b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item>
<shape android:shape="rectangle">
- <corners android:radius="16dp"/>
+ <corners android:radius="@dimen/ksh_button_corner_radius"/>
<solid android:color="?androidprv:attr/materialColorPrimary"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
index 6ce3eaecc147..aa0b2689f5bf 100644
--- a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml
@@ -17,8 +17,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?android:attr/colorBackground"/>
- <corners android:topLeftRadius="16dp"
- android:topRightRadius="16dp"
+ <corners android:topLeftRadius="@dimen/ksh_dialog_top_corner_radius"
+ android:topRightRadius="@dimen/ksh_dialog_top_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
-</shape> \ No newline at end of file
+</shape>
diff --git a/packages/SystemUI/res/drawable/shortcut_search_background.xml b/packages/SystemUI/res/drawable/shortcut_search_background.xml
index 66fc191cb736..d6847f0abb8d 100644
--- a/packages/SystemUI/res/drawable/shortcut_search_background.xml
+++ b/packages/SystemUI/res/drawable/shortcut_search_background.xml
@@ -19,8 +19,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
- <corners android:radius="24dp" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright" />
+ <corners android:radius="@dimen/ksh_search_box_corner_radius" />
</shape>
</item>
-</layer-list> \ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
index 6c4d4fbcc1fe..2675906580f1 100644
--- a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
+++ b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml
@@ -20,7 +20,7 @@
<shape android:shape="oval">
<size android:width="24dp"
android:height="24dp" />
- <solid android:color="?androidprv:attr/colorSurface"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
index ae243130e537..14b3b55df0a4 100644
--- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
+++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml
@@ -3,6 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/editor_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
index a0051008ddd6..5ab23271922c 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml
@@ -15,13 +15,14 @@
~ limitations under the License
-->
<com.android.systemui.statusbar.KeyboardShortcutAppItemLayout
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:background="@drawable/list_item_background"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
+ android:minHeight="@dimen/ksh_app_item_minimum_height"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/keyboard_shortcuts_icon"
@@ -39,7 +40,8 @@
android:layout_height="wrap_content"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp"
android:maxLines="5"
android:singleLine="false"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
index 530e46eb1c40..76e5b12bdcd6 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml
@@ -15,6 +15,6 @@
limitations under the License
-->
<View xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="0dp"
+ android:layout_marginTop="@dimen/ksh_category_separator_margin"
+ android:layout_marginBottom="@dimen/ksh_category_separator_margin"
style="@style/ShortcutHorizontalDivider" />
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
index 4f100f6b94d1..6e7fde68ca04 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml
@@ -16,10 +16,12 @@
~ limitations under the License
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
- android:fontFamily="sans-serif-medium"
+ android:textColor="?androidprv:attr/materialColorPrimary"
android:importantForAccessibility="yes"
android:paddingTop="20dp"
android:paddingBottom="10dp"/>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index f6042e467987..2cfd644e9d62 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -16,15 +16,21 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:background="@drawable/shortcut_dialog_bg"
android:layout_width="@dimen/ksh_layout_width"
android:layout_height="wrap_content"
android:orientation="vertical">
+
+ <com.google.android.material.bottomsheet.BottomSheetDragHandleView
+ android:id="@+id/drag_handle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
<TextView
android:id="@+id/shortcut_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="40dp"
android:layout_gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorPrimary"
@@ -39,44 +45,47 @@
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:padding="16dp"
android:background="@drawable/shortcut_search_background"
android:drawableStart="@drawable/ic_shortcutlist_search"
android:drawablePadding="15dp"
android:singleLine="true"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
android:inputType="text"
android:textDirection="locale"
android:textAlignment="viewStart"
android:hint="@string/keyboard_shortcut_search_list_hint"
- android:textColorHint="?android:attr/textColorTertiary" />
+ android:textAppearance="@android:style/TextAppearance.Material"
+ android:textSize="16sp"
+ android:textColorHint="?androidprv:attr/materialColorOutline" />
<ImageButton
android:id="@+id/keyboard_shortcuts_search_cancel"
android:layout_gravity="center_vertical|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="49dp"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:padding="16dp"
android:contentDescription="@string/keyboard_shortcut_clear_text"
android:src="@drawable/ic_shortcutlist_search_button_cancel"
android:background="@drawable/shortcut_search_cancel_button"
style="@android:style/Widget.Material.Button.Borderless.Small"
- android:pointerIcon="arrow" />
+ android:pointerIcon="arrow"
+ android:visibility="gone" />
</FrameLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
android:layout_marginEnd="0dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_vertical"
+ android:layout_gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/shortcut_system"
@@ -113,29 +122,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:layout_gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/keyboard_shortcut_search_list_no_result"/>
- <ScrollView
+ <androidx.core.widget.NestedScrollView
android:id="@+id/keyboard_shortcuts_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
- android:layout_marginStart="49dp"
- android:layout_marginEnd="49dp"
+ android:layout_marginStart="@dimen/ksh_container_horizontal_margin"
+ android:layout_marginEnd="@dimen/ksh_container_horizontal_margin"
android:overScrollMode="never"
- android:layout_marginBottom="16dp"
+ android:clipToPadding="false"
android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
- </ScrollView>
+ </androidx.core.widget.NestedScrollView>
<!-- Required for stretching to full available height when the items in the scroll view
occupy less space then the full height -->
<View
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 55606aa0bc82..56ebc0668097 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -94,4 +94,7 @@
<dimen name="keyguard_indication_margin_bottom">8dp</dimen>
<dimen name="lock_icon_margin_bottom">24dp</dimen>
+
+ <!-- Keyboard shortcuts helper -->
+ <dimen name="ksh_container_horizontal_margin">48dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe8f2fff550a..b95ee56e9e48 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -978,6 +978,7 @@
<dimen name="assist_disclosure_shadow_thickness">1.5dp</dimen>
<!-- Keyboard shortcuts helper -->
+ <dimen name="ksh_container_horizontal_margin">32dp</dimen>
<dimen name="ksh_layout_width">@dimen/match_parent</dimen>
<dimen name="ksh_item_text_size">14sp</dimen>
<dimen name="ksh_item_padding">0dp</dimen>
@@ -985,6 +986,11 @@
<dimen name="ksh_icon_scaled_size">18dp</dimen>
<dimen name="ksh_key_view_padding_vertical">4dp</dimen>
<dimen name="ksh_key_view_padding_horizontal">12dp</dimen>
+ <dimen name="ksh_button_corner_radius">12dp</dimen>
+ <dimen name="ksh_dialog_top_corner_radius">28dp</dimen>
+ <dimen name="ksh_search_box_corner_radius">100dp</dimen>
+ <dimen name="ksh_app_item_minimum_height">64dp</dimen>
+ <dimen name="ksh_category_separator_margin">16dp</dimen>
<!-- The size of corner radius of the arrow in the onboarding toast. -->
<dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen>
@@ -1867,6 +1873,10 @@
.2
</item>
+ <item name="dream_overlay_bouncer_min_region_screen_percentage" format="float" type="dimen">
+ .05
+ </item>
+
<!-- The padding applied to the dream overlay container -->
<dimen name="dream_overlay_container_padding_start">0dp</dimen>
<dimen name="dream_overlay_container_padding_end">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 71353b6774af..244603908c65 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2023,12 +2023,12 @@
<!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
<string name="group_system_quick_memo">Take a note</string>
- <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
- <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string>
- <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] -->
- <string name="system_multitasking_rhs">Enter split screen with current app to RHS</string>
- <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] -->
- <string name="system_multitasking_lhs">Enter split screen with current app to LHS</string>
+ <!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_system_multitasking">Multitasking</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app on the right [CHAR LIMIT=70] -->
+ <string name="system_multitasking_rhs">Use split screen with current app on the right</string>
+ <!-- User visible title for the keyboard shortcut that enters split screen with current app on the left [CHAR LIMIT=70] -->
+ <string name="system_multitasking_lhs">Use split screen with current app on the left</string>
<!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] -->
<string name="system_multitasking_full_screen">Switch from split screen to full screen</string>
<!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6462d02de481..9da4f79e33df 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -224,6 +224,7 @@
<style name="TextAppearance.AuthCredential.ContentViewListItem" parent="TextAppearance.Material3.BodySmall">
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
<item name="android:paddingTop">@dimen/biometric_prompt_content_list_item_padding_top</item>
+ <item name="android:breakStrategy">high_quality</item>
</style>
<style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium">
@@ -365,6 +366,21 @@
<item name="android:layout_height">wrap_content</item>
</style>
+ <style name="KeyboardShortcutHelper" parent="@android:style/Theme.DeviceDefault.Settings">
+ <!-- Needed to be able to use BottomSheetDragHandleView -->
+ <item name="android:windowActionBar">false</item>
+ <item name="bottomSheetDragHandleStyle">@style/KeyboardShortcutHelper.BottomSheet.DragHandle</item>
+ </style>
+
+ <style name="KeyboardShortcutHelper.BottomSheet.DragHandle" parent="Widget.Material3.BottomSheet.DragHandle">
+ <item name="tint">?androidprv:attr/materialColorOutlineVariant</item>
+ </style>
+
+ <style name="KeyboardShortcutHelper.BottomSheetDialogAnimation">
+ <item name="android:windowEnterAnimation">@anim/slide_in_up</item>
+ <item name="android:windowExitAnimation">@anim/slide_out_down</item>
+ </style>
+
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer" />
<style name="Animation" />
@@ -1597,14 +1613,15 @@
<item name="android:layout_marginEnd">12dp</item>
<item name="android:paddingLeft">24dp</item>
<item name="android:paddingRight">24dp</item>
- <item name="android:minHeight">40dp</item>
+ <item name="android:minHeight">36dp</item>
+ <item name="android:minWidth">120dp</item>
<item name="android:stateListAnimator">@*android:anim/flat_button_state_list_anim_material</item>
<item name="android:pointerIcon">arrow</item>
</style>
<style name="ShortcutHorizontalDivider">
- <item name="android:layout_width">120dp</item>
- <item name="android:layout_height">1dp</item>
+ <item name="android:layout_width">132dp</item>
+ <item name="android:layout_height">2dp</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:background">?android:attr/dividerHorizontal</item>
</style>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 42ba05cff906..fbe139930e11 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -58,7 +58,7 @@ android_library {
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
"WindowManager-Shell-shared",
- "tracinglib-platform",
+ "//frameworks/libs/systemui:tracinglib-platform",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
@@ -68,7 +68,7 @@ android_library {
"kotlinx_coroutines",
"dagger2",
"jsr330",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 46329148a659..dcc14409f046 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -155,5 +155,10 @@ interface ISystemUiProxy {
*/
oneway void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) = 54;
- // Next id = 55
+ /**
+ * Set the override value for home button long press duration in ms and slop multiplier.
+ */
+ oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) = 55;
+
+ // Next id = 56
}
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c4d282e24a92..a667de2351d7 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -28,10 +28,14 @@ import android.os.ParcelFileDescriptor
import android.os.UserHandle
import android.util.Log
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.communal.domain.backup.CommunalPrefsBackupHelper
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
+import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManagerImpl
/**
@@ -53,6 +57,8 @@ open class BackupHelper : BackupAgentHelper() {
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
"systemui.keyguard.quickaffordance.shared_preferences"
+ private const val COMMUNAL_PREFS_BACKUP_KEY =
+ "systemui.communal.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
@@ -75,6 +81,15 @@ open class BackupHelper : BackupAgentHelper() {
userId = userHandle.identifier,
),
)
+ if (communalEnabled()) {
+ addHelper(
+ COMMUNAL_PREFS_BACKUP_KEY,
+ CommunalPrefsBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ )
+ )
+ }
}
override fun onRestoreFinished() {
@@ -100,6 +115,10 @@ open class BackupHelper : BackupAgentHelper() {
}
}
+ private fun communalEnabled(): Boolean {
+ return resources.getBoolean(R.bool.config_communalServiceEnabled) && communalHub()
+ }
+
/**
* Helper class for restoring files ONLY if they are not present.
*
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
index fd7e98f1865c..9f594feb03ab 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
@@ -30,7 +30,7 @@ enum class ColorProfile {
Active,
// Yellow for e.g., battery saver
Warning,
- // Red for e.t., low battery
+ // Red for e.g., low battery
Error,
}
@@ -108,17 +108,17 @@ sealed interface BatteryColors {
// 22% alpha white
override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+ // GM Gray 500
+ override val fill = Color.parseColor("#9AA0A6")
// GM Gray 600
- override val fill = Color.parseColor("#80868B")
- // GM Gray 700
- override val fillOnly = Color.parseColor("#5F6368")
+ override val fillOnly = Color.parseColor("#80868B")
- // GM Green 700
- override val activeFill = Color.parseColor("#188038")
+ // GM Green 500
+ override val activeFill = Color.parseColor("#34A853")
// GM Yellow 500
override val warnFill = Color.parseColor("#FBBC04")
- // GM Red 600
- override val errorFill = Color.parseColor("#D93025")
+ // GM Red 500
+ override val errorFill = Color.parseColor("#EA4335")
}
/** Color scheme appropriate for dark mode (light icons) */
@@ -132,12 +132,12 @@ sealed interface BatteryColors {
// GM Gray 400
override val fillOnly = Color.parseColor("#BDC1C6")
- // GM Green 500
- override val activeFill = Color.parseColor("#34A853")
- // GM Yellow
- override val warnFill = Color.parseColor("#FBBC04")
- // GM Red 600
- override val errorFill = Color.parseColor("#D93025")
+ // GM Green 700
+ override val activeFill = Color.parseColor("#188038")
+ // GM Yellow 700
+ override val warnFill = Color.parseColor("#F29900")
+ // GM Red 700
+ override val errorFill = Color.parseColor("#C5221F")
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 88aef5675240..7ccac03bcac6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -31,18 +31,19 @@ import android.text.style.BulletSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.ViewTreeObserver
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Space
import android.widget.TextView
-import androidx.lifecycle.lifecycleScope
import com.android.settingslib.Utils
import com.android.systemui.biometrics.ui.BiometricPromptLayout
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import kotlin.math.ceil
-import kotlinx.coroutines.launch
+
+private const val TAG = "BiometricCustomizedViewBinder"
/** Sub-binder for [BiometricPromptLayout.customized_view_container]. */
object BiometricCustomizedViewBinder {
@@ -52,21 +53,20 @@ object BiometricCustomizedViewBinder {
legacyCallback: Spaghetti.Callback
) {
customizedViewContainer.repeatWhenAttached { containerView ->
- lifecycleScope.launch {
- if (contentView == null) {
- containerView.visibility = View.GONE
- return@launch
- }
+ if (contentView == null) {
+ containerView.visibility = View.GONE
+ return@repeatWhenAttached
+ }
- containerView.width { containerWidth ->
- if (containerWidth == 0) {
- return@width
- }
- (containerView as LinearLayout).addView(
- contentView.toView(containerView.context, containerWidth, legacyCallback)
- )
- containerView.visibility = View.VISIBLE
+ containerView.width { containerWidth ->
+ if (containerWidth == 0) {
+ return@width
}
+ (containerView as LinearLayout).addView(
+ contentView.toView(containerView.context, containerWidth, legacyCallback),
+ LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+ )
+ containerView.visibility = View.VISIBLE
}
}
}
@@ -118,51 +118,42 @@ private fun PromptVerticalListContentView.initLayout(
containerViewWidth: Int
): View {
val inflater = LayoutInflater.from(context)
- val resources = context.resources
+ context.resources
val contentView =
inflater.inflateContentView(
R.layout.biometric_prompt_vertical_list_content_layout,
description
)
+ val listItemsToShow = ArrayList<PromptContentItem>(listItems)
// Show two column by default, once there is an item exceeding max lines, show single
// item instead.
val showTwoColumn =
- listItems.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) }
- var currRowView = createNewRowLayout(inflater)
- for (item in listItems) {
+ listItemsToShow.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) }
+ // If should show two columns and there are more than one items, make listItems always have odd
+ // number items.
+ if (showTwoColumn && listItemsToShow.size > 1 && listItemsToShow.size % 2 == 1) {
+ listItemsToShow.add(dummyItem())
+ }
+ var currRow = createNewRowLayout(inflater)
+ for (i in 0 until listItemsToShow.size) {
+ val item = listItemsToShow[i]
val itemView = item.toView(context, inflater)
- // If this item will be in the first row (contentView only has description view) and
- // description is empty, remove top padding of this item.
- if (contentView.childCount == 1 && description.isNullOrEmpty()) {
- itemView.setPadding(
- itemView.paddingLeft,
- 0,
- itemView.paddingRight,
- itemView.paddingBottom
- )
- }
- currRowView.addView(itemView)
+ contentView.removeTopPaddingForFirstRow(description, itemView)
- // If this is the first item in the current row, add space behind it.
- if (currRowView.childCount == 1 && showTwoColumn) {
- currRowView.addSpaceView(
- resources.getDimensionPixelSize(
- R.dimen.biometric_prompt_content_space_width_between_items
- ),
- MATCH_PARENT
- )
+ // If there should be two column, and there is already one item in the current row, add
+ // space between two items.
+ if (showTwoColumn && currRow.childCount == 1) {
+ currRow.addSpaceViewBetweenListItem()
}
+ currRow.addView(itemView)
- // If there are already two items (plus the space view) in the current row, or it
- // should be one column, start a new row
- if (currRowView.childCount == 3 || !showTwoColumn) {
- contentView.addView(currRowView)
- currRowView = createNewRowLayout(inflater)
+ // If there should be one column, or there are already two items (plus the space view) in
+ // the current row, or it's already the last item, start a new row
+ if (!showTwoColumn || currRow.childCount == 3 || i == listItemsToShow.size - 1) {
+ contentView.addView(currRow)
+ currRow = createNewRowLayout(inflater)
}
}
- if (currRowView.childCount > 0) {
- contentView.addView(currRowView)
- }
return contentView
}
@@ -170,10 +161,6 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
}
-private fun LinearLayout.addSpaceView(width: Int, height: Int) {
- addView(Space(context), LinearLayout.LayoutParams(width, height))
-}
-
private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
context: Context,
containerViewWidth: Int,
@@ -194,7 +181,10 @@ private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
val contentViewPadding =
resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal)
val listItemPadding = getListItemPadding(resources)
- val maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding
+ var maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding
+ // Reduce maxWidth a bit since paint#measureText is not accurate. See b/330909104 for
+ // more context.
+ maxWidth -= contentViewPadding / 2
val paint = TextPaint()
val attributes =
@@ -224,6 +214,7 @@ private fun PromptContentItem.toView(
inflater: LayoutInflater,
): TextView {
val resources = context.resources
+ // Somehow xml layout params settings doesn't work, set it again here.
val textView =
inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView
val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
@@ -251,6 +242,29 @@ private fun PromptContentItem.toView(
return textView
}
+/* [contentView] function */
+private fun LinearLayout.addSpaceViewBetweenListItem() =
+ addView(
+ Space(context),
+ LinearLayout.LayoutParams(
+ resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_content_space_width_between_items
+ ),
+ MATCH_PARENT
+ )
+ )
+
+/* [contentView] function*/
+private fun LinearLayout.removeTopPaddingForFirstRow(description: String?, itemView: TextView) {
+ // If this item will be in the first row (contentView only has description view and
+ // description is empty), remove top padding of this item.
+ if (description.isNullOrEmpty() && childCount == 1) {
+ itemView.setPadding(itemView.paddingLeft, 0, itemView.paddingRight, itemView.paddingBottom)
+ }
+}
+
+private fun dummyItem(): PromptContentItem = PromptContentItemPlainText("")
+
private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
var listItemPadding =
resources.getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index f1a0e5e3539c..78811a96a026 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -12,14 +12,15 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
data class LegacyBouncerDependencies
@Inject
@@ -59,7 +60,7 @@ constructor(
private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
) {
fun bind(view: ViewGroup) {
- if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
+ if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
val deps = composeBouncerDependencies.get()
ComposeBouncerViewBinder.bind(
view,
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index bb201b624a71..a43447f7fcf4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -23,14 +23,20 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.pm.PackageManager;
+import android.graphics.Insets;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.text.Editable;
import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.systemui.res.R;
/**
@@ -53,6 +59,24 @@ public class EditTextActivity extends Activity
mEditText = findViewById(R.id.edit_text);
mAttribution = findViewById(R.id.attribution);
mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class));
+
+ findViewById(R.id.editor_root).setOnApplyWindowInsetsListener(
+ new View.OnApplyWindowInsetsListener() {
+ @NonNull
+ @Override
+ public WindowInsets onApplyWindowInsets(@NonNull View view,
+ @NonNull WindowInsets windowInsets) {
+ Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) view.getLayoutParams();
+ layoutParams.leftMargin = insets.left;
+ layoutParams.bottomMargin = insets.bottom;
+ layoutParams.rightMargin = insets.right;
+ layoutParams.topMargin = insets.top;
+ view.setLayoutParams(layoutParams);
+ return WindowInsets.CONSUMED;
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 0e9b32ffd12a..40d744015498 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -17,8 +17,11 @@
package com.android.systemui.communal.data.repository
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.UserInfo
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
@@ -30,15 +33,18 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
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.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -65,14 +71,36 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
private val userFileManager: UserFileManager,
+ broadcastDispatcher: BroadcastDispatcher,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) : CommunalPrefsRepository {
private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl")
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
override val isCtaDismissed: Flow<Boolean> =
- userRepository.selectedUserInfo
+ combine(
+ userRepository.selectedUserInfo,
+ // Make sure combine can emit even if we never get a Backup & Restore event,
+ // which is the most common case as restoration only happens on initial device
+ // setup.
+ backupRestorationEvents.emitOnStart().onEach {
+ logger.i("Restored state for communal preferences.")
+ },
+ ) { user, _ ->
+ user
+ }
.flatMapLatest(::observeCtaDismissState)
.logDiffsForTable(
tableLogBuffer = tableLogBuffer,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt
new file mode 100644
index 000000000000..55c6ec88bd11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Helper to backup & restore the shared preferences in glanceable hub for the current user. */
+class CommunalPrefsBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ UserFileManagerImpl.createFile(
+ userId = userId,
+ fileName = FILE_NAME,
+ )
+ .path
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 82834387f7b8..f779ac8fb0bb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -1,15 +1,11 @@
package com.android.systemui.deviceentry.data.repository
-import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
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.keyguard.data.repository.KeyguardRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
import dagger.Module
@@ -17,38 +13,20 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Interface for classes that can access device-entry-related application state. */
interface DeviceEntryRepository {
/**
- * Whether the device is unlocked.
- *
- * A device that is not yet unlocked requires unlocking by completing an authentication
- * challenge according to the current authentication method, unless in cases when the current
- * authentication method is not "secure" (for example, None); in such cases, the value of this
- * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
- * by the user to proceed.
- */
- val isUnlocked: StateFlow<Boolean>
-
- /**
* Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
* chosen any secure authentication method and even if they set the lockscreen to be dismissed
* when the user swipes on it.
*/
suspend fun isLockscreenEnabled(): Boolean
- /** Report successful authentication for device entry. */
- fun reportSuccessfulAuthentication()
-
/**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed.
@@ -73,53 +51,8 @@ constructor(
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
private val keyguardBypassController: KeyguardBypassController,
- keyguardStateController: KeyguardStateController,
- keyguardRepository: KeyguardRepository,
) : DeviceEntryRepository {
- private val _isUnlocked = MutableStateFlow(false)
-
- private val isUnlockedReportedByLegacyKeyguard =
- conflatedCallbackFlow {
- val callback =
- object : KeyguardStateController.Callback {
- override fun onUnlockedChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isUnlocked due to onUnlockedChanged"
- )
- }
-
- override fun onKeyguardShowingChanged() {
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "updated isUnlocked due to onKeyguardShowingChanged"
- )
- }
- }
-
- keyguardStateController.addCallback(callback)
- // Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- keyguardStateController.isUnlocked,
- TAG,
- "initial isKeyguardUnlocked"
- )
-
- awaitClose { keyguardStateController.removeCallback(callback) }
- }
- .distinctUntilChanged()
- .onEach { _isUnlocked.value = it }
- .stateIn(
- applicationScope,
- SharingStarted.Eagerly,
- initialValue = false,
- )
-
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
val selectedUserId = userRepository.getSelectedUserInfo().id
@@ -127,11 +60,6 @@ constructor(
}
}
- override fun reportSuccessfulAuthentication() {
- Log.d(TAG, "Successful authentication reported.")
- _isUnlocked.value = true
- }
-
override val isBypassEnabled: StateFlow<Boolean> =
conflatedCallbackFlow {
val listener =
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index c4e0ef7d082d..ec574d2d031d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthentication
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -58,6 +59,9 @@ constructor(
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+ val fingerprintSuccess: Flow<SuccessFingerprintAuthenticationStatus> =
+ repository.authenticationStatus.filterIsInstance<SuccessFingerprintAuthenticationStatus>()
+
/**
* Whether fingerprint authentication is currently allowed for the user. This is true if the
* user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index fa2421a3516d..5c1ca646529e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -26,7 +26,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
@@ -35,14 +34,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -65,8 +61,7 @@ constructor(
private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val trustInteractor: TrustInteractor,
- flags: SceneContainerFlags,
- deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
@@ -78,7 +73,14 @@ constructor(
* of this flow will always be `true`, even if the lockscreen is showing and still needs to be
* dismissed by the user to proceed.
*/
- val isUnlocked: StateFlow<Boolean> = deviceUnlockedInteractor.isDeviceUnlocked
+ val isUnlocked: StateFlow<Boolean> =
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .map { it.isUnlocked }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked,
+ )
/**
* Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method).
@@ -100,17 +102,6 @@ constructor(
)
/**
- * Whether the user is currently authenticated by a TrustAgent like trusted device, location,
- * etc., or by face auth.
- */
- private val isPassivelyAuthenticated =
- merge(
- trustInteractor.isTrusted,
- faceAuthInteractor.authenticated,
- )
- .onStart { emit(false) }
-
- /**
* Whether it's currently possible to swipe up to enter the device without requiring
* authentication or when the device is already authenticated using a passive authentication
* mechanism like face or trust manager. This returns `false` whenever the lockscreen has been
@@ -129,10 +120,13 @@ constructor(
authenticationInteractor.authenticationMethod.map {
it == AuthenticationMethodModel.None && repository.isLockscreenEnabled()
},
- isPassivelyAuthenticated,
+ deviceUnlockedInteractor.deviceUnlockStatus,
isDeviceEntered
- ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered ->
- (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered
+ ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered ->
+ (isSwipeAuthMethod ||
+ (deviceUnlockStatus.isUnlocked &&
+ deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
+ !isDeviceEntered
}
.stateIn(
scope = applicationScope,
@@ -235,7 +229,8 @@ constructor(
* `false` if the device can be entered without authenticating first.
*/
suspend fun isAuthenticationRequired(): Boolean {
- return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure
+ return !deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked &&
+ authenticationInteractor.getAuthenticationMethod().isSecure
}
/**
@@ -246,18 +241,6 @@ constructor(
*/
val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
- init {
- if (flags.isEnabled()) {
- applicationScope.launch {
- authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful ->
- if (isSuccessful) {
- repository.reportSuccessfulAuthentication()
- }
- }
- }
- }
- }
-
private val wasRebootedForMainlineUpdate
get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index b0495fb8e819..098ede30d618 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -21,13 +21,23 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceUnlockedInteractor
@Inject
@@ -35,28 +45,63 @@ constructor(
@Application private val applicationScope: CoroutineScope,
authenticationInteractor: AuthenticationInteractor,
deviceEntryRepository: DeviceEntryRepository,
+ trustInteractor: TrustInteractor,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val powerInteractor: PowerInteractor,
) {
+ private val deviceUnlockSource =
+ merge(
+ fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint },
+ faceAuthInteractor.authenticated
+ .filter { it }
+ .map {
+ if (deviceEntryRepository.isBypassEnabled.value) {
+ DeviceUnlockSource.FaceWithBypass
+ } else {
+ DeviceUnlockSource.FaceWithoutBypass
+ }
+ },
+ trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent },
+ authenticationInteractor.onAuthenticationResult
+ .filter { it }
+ .map { DeviceUnlockSource.BouncerInput }
+ )
+
/**
- * Whether the device is unlocked.
+ * Whether the device is unlocked or not, along with the information about the authentication
+ * method that was used to unlock the device.
*
* A device that is not yet unlocked requires unlocking by completing an authentication
* challenge according to the current authentication method, unless in cases when the current
* authentication method is not "secure" (for example, None and Swipe); in such cases, the value
- * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
- * dismissed by the user to proceed.
+ * of this flow will always be an instance of [DeviceUnlockStatus] with
+ * [DeviceUnlockStatus.deviceUnlockSource] as null and [DeviceUnlockStatus.isUnlocked] set to
+ * true, even if the lockscreen is showing and still needs to be dismissed by the user to
+ * proceed.
*/
- val isDeviceUnlocked: StateFlow<Boolean> =
- combine(
- deviceEntryRepository.isUnlocked,
- authenticationInteractor.authenticationMethod,
- ) { isUnlocked, authenticationMethod ->
- (!authenticationMethod.isSecure || isUnlocked) &&
- authenticationMethod != AuthenticationMethodModel.Sim
+ val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
+ authenticationInteractor.authenticationMethod
+ .flatMapLatest { authMethod ->
+ if (!authMethod.isSecure) {
+ flowOf(DeviceUnlockStatus(true, null))
+ } else if (authMethod == AuthenticationMethodModel.Sim) {
+ // Device is locked if SIM is locked.
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ powerInteractor.isAsleep.flatMapLatest { isAsleep ->
+ if (isAsleep) {
+ flowOf(DeviceUnlockStatus(false, null))
+ } else {
+ deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ }
+ }
+ }
}
.stateIn(
scope = applicationScope,
started = SharingStarted.Eagerly,
- initialValue = false,
+ initialValue = DeviceUnlockStatus(false, null),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt
new file mode 100644
index 000000000000..619c2400ec72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/**
+ * Source of the device unlock.
+ *
+ * @property dismissesLockscreen whether unlock with this authentication method dismisses the
+ * lockscreen and enters the device.
+ */
+sealed class DeviceUnlockSource(val dismissesLockscreen: Boolean) {
+
+ data object Fingerprint : DeviceUnlockSource(true)
+ data object FaceWithBypass : DeviceUnlockSource(dismissesLockscreen = true)
+ data object FaceWithoutBypass : DeviceUnlockSource(dismissesLockscreen = false)
+ data object TrustAgent : DeviceUnlockSource(dismissesLockscreen = false)
+ data object BouncerInput : DeviceUnlockSource(dismissesLockscreen = true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt
new file mode 100644
index 000000000000..f694c331bd7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.deviceentry.shared.model
+
+/**
+ * Wrapper class that combines whether device is unlocked or not, along with the authentication
+ * method used to unlock the device.
+ *
+ * @property isUnlocked whether device is unlocked or not.
+ * @property deviceUnlockSource source that unlocked the device, null if lockscreen is not secure or
+ * if [isUnlocked] is false.
+ */
+data class DeviceUnlockStatus(
+ val isUnlocked: Boolean,
+ val deviceUnlockSource: DeviceUnlockSource?
+) {
+ init {
+ assert(isUnlocked || deviceUnlockSource == null) {
+ "deviceUnlockSource must be null when device is locked."
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 926f7f1aee48..75c50fd5f586 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.touch;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -81,6 +82,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
private final LockPatternUtils mLockPatternUtils;
private final UserTracker mUserTracker;
private final float mBouncerZoneScreenPercentage;
+ private final float mMinBouncerZoneScreenPercentage;
private final ScrimManager mScrimManager;
private ScrimController mCurrentScrimController;
@@ -222,6 +224,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
@Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
FlingAnimationUtils flingAnimationUtilsClosing,
@Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+ @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
UiEventLogger uiEventLogger) {
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
@@ -229,6 +232,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
mLockPatternUtils = lockPatternUtils;
mUserTracker = userTracker;
mBouncerZoneScreenPercentage = swipeRegionPercentage;
+ mMinBouncerZoneScreenPercentage = minRegionPercentage;
mFlingAnimationUtils = flingAnimationUtils;
mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
mValueAnimatorCreator = valueAnimatorCreator;
@@ -237,24 +241,27 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final int width = bounds.width();
final int height = bounds.height();
-
- if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
- region.op(new Rect(0, 0, width,
- Math.round(
- height * mBouncerZoneScreenPercentage)),
- Region.Op.UNION);
- } else {
- region.op(new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width,
- height),
- Region.Op.UNION);
+ final float minBouncerHeight = height * mMinBouncerZoneScreenPercentage;
+ final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
+
+ final boolean isBouncerShowing =
+ mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false);
+ final Rect normalRegion = isBouncerShowing
+ ? new Rect(0, 0, width, Math.round(height * mBouncerZoneScreenPercentage))
+ : new Rect(0, Math.round(height * (1 - mBouncerZoneScreenPercentage)),
+ width, height);
+
+ if (!isBouncerShowing && exclusionRect != null) {
+ int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
+ normalRegion.top = Math.max(normalRegion.top, lowestBottom);
}
+ region.union(normalRegion);
}
+
@Override
public void onSessionStart(TouchSession session) {
mVelocityTracker = mVelocityTrackerFactory.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index e5c705f608b9..13588c2d45fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -87,7 +87,7 @@ public class CommunalTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 55a9c0c4de99..3b22b31de121 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -18,9 +18,15 @@ package com.android.systemui.dreams.touch;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.systemui.shared.Flags.bouncerAreaExclusion;
+
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.RemoteException;
+import android.util.Log;
import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -31,6 +37,8 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -58,8 +66,23 @@ import javax.inject.Inject;
public class DreamOverlayTouchMonitor {
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
- private final Executor mExecutor;
+ public String TAG = "DreamOverlayTouchMonitor";
+ private final Executor mMainExecutor;
+ private final Executor mBackgroundExecutor;
private final Lifecycle mLifecycle;
+ private Rect mExclusionRect = null;
+
+ private ISystemGestureExclusionListener mGestureExclusionListener =
+ new ISystemGestureExclusionListener.Stub() {
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion,
+ Region systemGestureExclusionUnrestricted) {
+ mExclusionRect = systemGestureExclusion.getBounds();
+ }
+ };
+
+
/**
* Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
@@ -67,7 +90,7 @@ public class DreamOverlayTouchMonitor {
private ListenableFuture<DreamTouchHandler.TouchSession> push(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
+ mMainExecutor.execute(() -> {
if (!mActiveTouchSessions.remove(touchSessionImpl)) {
completer.set(null);
return;
@@ -90,7 +113,7 @@ public class DreamOverlayTouchMonitor {
private ListenableFuture<DreamTouchHandler.TouchSession> pop(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
- mExecutor.execute(() -> {
+ mMainExecutor.execute(() -> {
if (mActiveTouchSessions.remove(touchSessionImpl)) {
touchSessionImpl.onRemoved();
@@ -240,6 +263,17 @@ public class DreamOverlayTouchMonitor {
*/
private void startMonitoring() {
stopMonitoring(true);
+ if (bouncerAreaExclusion()) {
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mWindowManagerService.registerSystemGestureExclusionListener(
+ mGestureExclusionListener, mDisplayId);
+ } catch (RemoteException e) {
+ // Handle the exception
+ Log.e(TAG, "Failed to register gesture exclusion listener", e);
+ }
+ });
+ }
mCurrentInputSession = mInputSessionFactory.create(
"dreamOverlay",
mInputEventListener,
@@ -252,6 +286,18 @@ public class DreamOverlayTouchMonitor {
* Destroys any active {@link InputSession}.
*/
private void stopMonitoring(boolean force) {
+ mExclusionRect = null;
+ if (bouncerAreaExclusion()) {
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mWindowManagerService.unregisterSystemGestureExclusionListener(
+ mGestureExclusionListener, mDisplayId);
+ } catch (RemoteException e) {
+ // Handle the exception
+ Log.e(TAG, "unregisterSystemGestureExclusionListener: failed", e);
+ }
+ });
+ }
if (mCurrentInputSession == null) {
return;
}
@@ -263,7 +309,7 @@ public class DreamOverlayTouchMonitor {
// When we stop monitoring touches, we must ensure that all active touch sessions and
// descendants informed of the removal so any cleanup for active tracking can proceed.
- mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
+ mMainExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
while (touchSession != null) {
touchSession.onRemoved();
touchSession = touchSession.getPredecessor();
@@ -295,11 +341,15 @@ public class DreamOverlayTouchMonitor {
if (!handler.isEnabled()) {
continue;
}
- final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
- TYPE_APPLICATION_OVERLAY);
-
- final Region initiationRegion = Region.obtain();
- handler.getTouchInitiationRegion(maxBounds, initiationRegion);
+ final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
+ TYPE_APPLICATION_OVERLAY);
+ final Region initiationRegion = Region.obtain();
+ Rect exclusionRect = null;
+ if (bouncerAreaExclusion()) {
+ exclusionRect = getCurrentExclusionRect();
+ }
+ handler.getTouchInitiationRegion(
+ maxBounds, initiationRegion, exclusionRect);
if (!initiationRegion.isEmpty()) {
// Initiation regions require a motion event to determine pointer location
@@ -335,6 +385,9 @@ public class DreamOverlayTouchMonitor {
.flatMap(Collection::stream)
.forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
}
+ private Rect getCurrentExclusionRect() {
+ return mExclusionRect;
+ }
};
/**
@@ -416,6 +469,9 @@ public class DreamOverlayTouchMonitor {
private InputSessionComponent.Factory mInputSessionFactory;
private InputSession mCurrentInputSession;
+ private final int mDisplayId;
+ private final IWindowManager mWindowManagerService;
+
/**
* Designated constructor for {@link DreamOverlayTouchMonitor}
@@ -432,15 +488,21 @@ public class DreamOverlayTouchMonitor {
@Inject
public DreamOverlayTouchMonitor(
@Main Executor executor,
+ @Background Executor backgroundExecutor,
Lifecycle lifecycle,
InputSessionComponent.Factory inputSessionFactory,
DisplayHelper displayHelper,
- Set<DreamTouchHandler> handlers) {
+ Set<DreamTouchHandler> handlers,
+ IWindowManager windowManagerService,
+ @DisplayId int displayId) {
+ mDisplayId = displayId;
mHandlers = handlers;
mInputSessionFactory = inputSessionFactory;
- mExecutor = executor;
+ mMainExecutor = executor;
+ mBackgroundExecutor = backgroundExecutor;
mLifecycle = lifecycle;
mDisplayHelper = displayHelper;
+ mWindowManagerService = windowManagerService;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
index 72ad45d1055c..1ec000835ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
@@ -104,7 +104,7 @@ public interface DreamTouchHandler {
* indicating the entire screen should be considered.
* @param region A {@link Region} that is passed in to the target entry touch region.
*/
- default void getTouchInitiationRegion(Rect bounds, Region region) {
+ default void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
index 6f05e83b22ba..e0bf52e81875 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -82,7 +82,7 @@ public class ShadeTouchHandler implements DreamTouchHandler {
}
@Override
- public void getTouchInitiationRegion(Rect bounds, Region region) {
+ public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index 8cf11a9817b7..a5db2ff81f99 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -21,10 +21,10 @@ import android.content.res.Resources;
import android.util.TypedValue;
import android.view.VelocityTracker;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -46,6 +46,9 @@ public class BouncerSwipeModule {
*/
public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
+ public static final String MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE =
+ "min_bouncer_zone_screen_percentage";
+
/**
* The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
*/
@@ -110,6 +113,18 @@ public class BouncerSwipeModule {
}
/**
+ * Provides the minimum region to start wipe gestures from.
+ */
+ @Provides
+ @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ public static float providesMinBouncerZoneScreenPercentage(@Main Resources resources) {
+ TypedValue typedValue = new TypedValue();
+ resources.getValue(R.dimen.dream_overlay_bouncer_min_region_screen_percentage,
+ typedValue, true);
+ return typedValue.getFloat();
+ }
+
+ /**
* Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
* a wrapper around {@link ValueAnimator}.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 73878b6780d9..640534cc9d34 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -410,13 +410,6 @@ object Flags {
val CLIPBOARD_SHARED_TRANSITIONS =
unreleasedFlag("clipboard_shared_transitions", teamfood = true)
- /**
- * Whether the compose bouncer is enabled. This ensures ProGuard can
- * remove unused code from our APK at compile time.
- */
- // TODO(b/280877228): Tracking Bug
- @JvmField val COMPOSE_BOUNCER_ENABLED = false
-
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index f128a80191df..2182fe378d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -193,7 +193,7 @@ constructor(
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is dismissible or not. */
- val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
+ val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
@Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
index 03ed5675a77a..4abd6c6a6453 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -107,7 +107,7 @@ constructor(
*/
val occludingActivityWillDismissKeyguard: StateFlow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- deviceUnlockedInteractor.get().isDeviceUnlocked
+ deviceUnlockedInteractor.get().deviceUnlockStatus.map { it.isUnlocked }
} else {
keyguardInteractor.isKeyguardDismissible
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index e861ddf69aa6..da9e00ddb6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,11 +16,8 @@
package com.android.systemui.mediaprojection.permission;
-import static android.Manifest.permission.LOG_COMPAT_CHANGE;
-import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
-import static android.media.projection.MediaProjectionManager.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -29,13 +26,11 @@ import static com.android.systemui.mediaprojection.permission.ScreenShareOptionK
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
-import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -113,7 +108,6 @@ public class MediaProjectionPermissionActivity extends Activity
}
@Override
- @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -241,10 +235,6 @@ public class MediaProjectionPermissionActivity extends Activity
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -256,7 +246,6 @@ public class MediaProjectionPermissionActivity extends Activity
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
- overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 8858041ae529..0f54e934f3cf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,12 +30,11 @@ class MediaProjectionPermissionDialogDelegate(
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
- private val forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
+ createOptionList(context, appName, mediaProjectionConfig),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -66,8 +65,7 @@ class MediaProjectionPermissionDialogDelegate(
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?,
- overrideDisableSingleAppOption: Boolean = false,
+ mediaProjectionConfig: MediaProjectionConfig?
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -82,13 +80,8 @@ class MediaProjectionPermissionDialogDelegate(
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
- // The single app option should only be disabled if there is an app name provided,
- // the client has setup a MediaProjection with
- // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
- // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
appName != null &&
- !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 4fe3a11078db..ade56c435421 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -85,6 +85,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -186,6 +187,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200;
private static final long AUTODIM_TIMEOUT_MS = 2250;
+ private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
private final Context mContext;
private final Bundle mSavedState;
@@ -223,6 +225,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final int mNavColorSampleMargin;
private EdgeBackGestureHandler mEdgeBackGestureHandler;
private NavigationBarFrame mFrame;
+ private MotionEvent mCurrentDownEvent;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -238,6 +241,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private int mLayoutDirection;
private Optional<Long> mHomeButtonLongPressDurationMs;
+ private Optional<Long> mOverrideHomeButtonLongPressDurationMs = Optional.empty();
+ private Optional<Float> mOverrideHomeButtonLongPressSlopMultiplier = Optional.empty();
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
private @Appearance int mAppearance;
@@ -405,6 +410,25 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ mOverrideHomeButtonLongPressDurationMs = Optional.of(duration)
+ .filter(value -> value > 0);
+ mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier)
+ .filter(value -> value > 0);
+ if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
+ Log.d(TAG, "Receive duration override: "
+ + mOverrideHomeButtonLongPressDurationMs.get());
+ }
+ if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
+ Log.d(TAG, "Receive slop multiplier override: "
+ + mOverrideHomeButtonLongPressSlopMultiplier.get());
+ }
+ if (mView != null) {
+ reconfigureHomeLongClick();
+ }
+ }
+
+ @Override
public void onHomeRotationEnabled(boolean enabled) {
mView.getRotationButtonController().setHomeRotationEnabled(enabled);
}
@@ -1016,7 +1040,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (mView.getHomeButton().getCurrentView() == null) {
return;
}
- if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
+ if (mHomeButtonLongPressDurationMs.isPresent()
+ || mOverrideHomeButtonLongPressDurationMs.isPresent()
+ || mOverrideHomeButtonLongPressSlopMultiplier.isPresent()
+ || !mLongPressHomeEnabled) {
mView.getHomeButton().getCurrentView().setLongClickable(false);
mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
mView.getHomeButton().setOnLongClickListener(null);
@@ -1038,6 +1065,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
pw.println(" mCurrentRotation=" + mCurrentRotation);
pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
+ pw.println(" mOverrideHomeButtonLongPressDurationMs="
+ + mOverrideHomeButtonLongPressDurationMs);
+ pw.println(" mOverrideHomeButtonLongPressSlopMultiplier="
+ + mOverrideHomeButtonLongPressSlopMultiplier);
pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled);
pw.println(" mNavigationBarWindowState="
+ windowStateToString(mNavigationBarWindowState));
@@ -1331,6 +1362,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
final Optional<CentralSurfaces> centralSurfacesOptional = mCentralSurfacesOptionalLazy.get();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
+ mCurrentDownEvent = MotionEvent.obtain(event);
mHomeBlockedThisTouch = false;
if (mTelecomManagerOptional.isPresent()
&& mTelecomManagerOptional.get().isRinging()) {
@@ -1342,9 +1377,45 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
}
if (mLongPressHomeEnabled) {
- mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
- mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
- });
+ if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
+ Log.d(TAG, "ACTION_DOWN Launcher override duration: "
+ + mOverrideHomeButtonLongPressDurationMs.get());
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ mOverrideHomeButtonLongPressDurationMs.get());
+ } else if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
+ // If override timeout doesn't exist but override touch slop exists, we use
+ // system default long press duration
+ Log.d(TAG, "ACTION_DOWN default duration: "
+ + ViewConfiguration.getLongPressTimeout());
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ ViewConfiguration.getLongPressTimeout());
+ } else {
+ mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
+ Log.d(TAG, "ACTION_DOWN original duration: " + longPressDuration);
+ mHandler.postDelayed(mOnVariableDurationHomeLongClick,
+ longPressDuration);
+ });
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) {
+ Log.w(TAG, "No callback. Don't handle touch slop.");
+ break;
+ }
+ float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f);
+ float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ float calculatedTouchSlop =
+ customSlopMultiplier * QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON * touchSlop;
+ float touchSlopSquared = calculatedTouchSlop * calculatedTouchSlop;
+
+ float dx = event.getX() - mCurrentDownEvent.getX();
+ float dy = event.getY() - mCurrentDownEvent.getY();
+ double distanceSquared = (dx * dx) + (dy * dy);
+ if (distanceSquared > touchSlopSquared) {
+ Log.i(TAG, "Touch slop passed. Abort.");
+ mView.abortCurrentGesture();
+ mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
}
break;
case MotionEvent.ACTION_UP:
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index dc42b5c35223..b27b974dc972 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -63,6 +63,7 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.DialogKt;
import java.util.ArrayList;
import java.util.List;
@@ -245,6 +246,10 @@ public class CastTile extends QSTileImpl<BooleanState> {
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG));
} else {
+ if (dialog.getWindow() != null) {
+ DialogKt.registerAnimationOnBackInvoked(dialog,
+ dialog.getWindow().getDecorView());
+ }
dialog.show();
}
});
@@ -272,7 +277,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
state.secondaryLabel = getDeviceName(device);
state.stateDescription = state.stateDescription + ","
+ mContext.getString(
- R.string.accessibility_cast_name, state.label);
+ R.string.accessibility_cast_name, state.label);
connecting = false;
break;
} else if (device.state == CastDevice.STATE_CONNECTING) {
@@ -342,14 +347,14 @@ public class CastTile extends QSTileImpl<BooleanState> {
};
private final SignalCallback mSignalCallback = new SignalCallback() {
- @Override
- public void setWifiIndicators(@NonNull WifiIndicators indicators) {
- // statusIcon.visible has the connected status information
- boolean enabledAndConnected = indicators.enabled
- && (indicators.qsIcon != null && indicators.qsIcon.visible);
- setCastTransportAllowed(enabledAndConnected);
- }
- };
+ @Override
+ public void setWifiIndicators(@NonNull WifiIndicators indicators) {
+ // statusIcon.visible has the connected status information
+ boolean enabledAndConnected = indicators.enabled
+ && (indicators.qsIcon != null && indicators.qsIcon.visible);
+ setCastTransportAllowed(enabledAndConnected);
+ }
+ };
private final HotspotController.Callback mHotspotCallback =
new HotspotController.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
new file mode 100644
index 000000000000..7117629622e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy
+
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.hardware.SensorPrivacyManager.Sensors.Sensor
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Observes SensorPrivacyToggle mode state changes providing the [SensorPrivacyToggleTileModel]. */
+class SensorPrivacyToggleTileDataInteractor
+@AssistedInject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val privacyController: IndividualSensorPrivacyController,
+ @Assisted @Sensor private val sensorId: Int,
+) : QSTileDataInteractor<SensorPrivacyToggleTileModel> {
+ @AssistedFactory
+ interface Factory {
+ fun create(@Sensor id: Int): SensorPrivacyToggleTileDataInteractor
+ }
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<SensorPrivacyToggleTileModel> =
+ conflatedCallbackFlow {
+ val callback =
+ IndividualSensorPrivacyController.Callback { sensor, blocked ->
+ if (sensor == sensorId) trySend(SensorPrivacyToggleTileModel(blocked))
+ }
+ privacyController.addCallback(callback) // does not emit an initial state
+ awaitClose { privacyController.removeCallback(callback) }
+ }
+ .onStart {
+ emit(SensorPrivacyToggleTileModel(privacyController.isSensorBlocked(sensorId)))
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+
+ override fun availability(user: UserHandle) =
+ flow { emit(isAvailable()) }.flowOn(bgCoroutineContext)
+
+ private suspend fun isAvailable(): Boolean {
+ return privacyController.supportsSensorToggle(sensorId) && isSensorDeviceConfigSet()
+ }
+
+ private suspend fun isSensorDeviceConfigSet(): Boolean =
+ withContext(bgCoroutineContext) {
+ try {
+ val deviceConfigName = getDeviceConfigName(sensorId)
+ return@withContext DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ deviceConfigName,
+ true
+ )
+ } catch (exception: IllegalArgumentException) {
+ Log.w(
+ TAG,
+ "isDeviceConfigSet for sensorId $sensorId: " +
+ "Defaulting to true due to exception. ",
+ exception
+ )
+ return@withContext true
+ }
+ }
+
+ private fun getDeviceConfigName(sensorId: Int): String {
+ if (sensorId == MICROPHONE) {
+ return "mic_toggle_enabled"
+ } else if (sensorId == CAMERA) {
+ return "camera_toggle_enabled"
+ } else {
+ throw IllegalArgumentException("getDeviceConfigName: unexpected sensorId: $sensorId")
+ }
+ }
+
+ private companion object {
+ const val TAG = "SensorPrivacyToggleTileException"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
new file mode 100644
index 000000000000..9711cb81e2c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager.Sensors.Sensor
+import android.hardware.SensorPrivacyManager.Sources.QS_TILE
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Handles sensor privacy toggle tile clicks and long clicks. */
+class SensorPrivacyToggleTileUserActionInteractor
+@AssistedInject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val activityStarter: ActivityStarter,
+ private val sensorPrivacyController: IndividualSensorPrivacyController,
+ private val safetyCenterManager: SafetyCenterManager,
+ @Assisted @Sensor private val sensorId: Int,
+) : QSTileUserActionInteractor<SensorPrivacyToggleTileModel> {
+ @AssistedFactory
+ interface Factory {
+ fun create(@Sensor id: Int): SensorPrivacyToggleTileUserActionInteractor
+ }
+
+ // should only be initialized in code known to run in background thread
+ private lateinit var longClickIntent: Intent
+
+ override suspend fun handleInput(input: QSTileInput<SensorPrivacyToggleTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ val blocked = input.data.isBlocked
+ if (
+ sensorPrivacyController.requiresAuthentication() &&
+ keyguardInteractor.isKeyguardDismissible.value &&
+ keyguardInteractor.isKeyguardShowing()
+ ) {
+ activityStarter.postQSRunnableDismissingKeyguard {
+ sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked)
+ }
+ return
+ }
+ sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked)
+ }
+ is QSTileUserAction.LongClick -> {
+ if (!::longClickIntent.isInitialized) {
+ longClickIntent =
+ Intent(
+ if (safetyCenterManager.isSafetyCenterEnabled) {
+ Settings.ACTION_PRIVACY_CONTROLS
+ } else {
+ Settings.ACTION_PRIVACY_SETTINGS
+ }
+ )
+ }
+ qsTileIntentUserActionHandler.handle(action.view, longClickIntent)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt
index dc804ca61dcf..04719afafa85 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model
-/** Constants that vary by compilation configuration. */
-public class Compile {
- /** Whether SystemUI was compiled in debug mode, and supports debug features */
- public static final boolean IS_DEBUG = true;
-}
+/**
+ * Sensor privacy toggle tile model.
+ *
+ * @param isBlocked is true when the sensor is blocked
+ */
+@JvmInline value class SensorPrivacyToggleTileModel(val isBlocked: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt
new file mode 100644
index 000000000000..2a9fd07a67cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import com.android.systemui.res.R
+
+sealed interface SensorPrivacyTileResources {
+ fun getIconRes(isBlocked: Boolean): Int
+ fun getTileLabelRes(): Int
+
+ data object CameraPrivacyTileResources : SensorPrivacyTileResources {
+ override fun getIconRes(isBlocked: Boolean): Int {
+ return if (isBlocked) {
+ R.drawable.qs_camera_access_icon_off
+ } else {
+ R.drawable.qs_camera_access_icon_on
+ }
+ }
+
+ override fun getTileLabelRes(): Int {
+ return R.string.quick_settings_camera_label
+ }
+ }
+
+ data object MicrophonePrivacyTileResources : SensorPrivacyTileResources {
+ override fun getIconRes(isBlocked: Boolean): Int {
+ return if (isBlocked) {
+ R.drawable.qs_mic_access_off
+ } else {
+ R.drawable.qs_mic_access_on
+ }
+ }
+
+ override fun getTileLabelRes(): Int {
+ return R.string.quick_settings_mic_label
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
new file mode 100644
index 000000000000..52622d26348d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.sensorprivacy.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Maps [SensorPrivacyToggleTileModel] to [QSTileState]. */
+class SensorPrivacyToggleTileMapper
+@AssistedInject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ @Assisted private val sensorPrivacyTileResources: SensorPrivacyTileResources,
+) : QSTileDataToStateMapper<SensorPrivacyToggleTileModel> {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ sensorPrivacyTileResources: SensorPrivacyTileResources
+ ): SensorPrivacyToggleTileMapper
+ }
+
+ override fun map(config: QSTileConfig, data: SensorPrivacyToggleTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(sensorPrivacyTileResources.getTileLabelRes())
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ sensorPrivacyTileResources.getIconRes(data.isBlocked),
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isBlocked) {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_blocked)
+ } else {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_available)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 7c1a2c032bea..f621f11fdaf2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -259,6 +259,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
@Override
+ public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ verifyCallerAndClearCallingIdentityPostMain("setOverrideHomeButtonLongPress",
+ () -> notifySetOverrideHomeButtonLongPress(duration, slopMultiplier));
+ }
+
+ @Override
public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
@@ -947,6 +953,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
+ private void notifySetOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).setOverrideHomeButtonLongPress(duration, slopMultiplier);
+ }
+ }
+
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1104,6 +1116,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
default void startAssistant(Bundle bundle) {}
default void setAssistantOverridesRequested(int[] invocationTypes) {}
default void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {}
+ /** Set override of home button long press duration and touch slop multiplier. */
+ default void setOverrideHomeButtonLongPress(long override, float slopMultiplier) {}
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 75bf131afdf9..2ccd3b9e9f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -162,7 +162,9 @@ constructor(
loggingReason: String,
transitionKey: TransitionKey? = null,
) {
- check(toScene != Scenes.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) {
+ check(
+ toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
+ ) {
"Cannot change to the Gone scene while the device is locked. Logging reason for scene" +
" change was: $loggingReason"
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f4292207f3a9..32d72e0bac22 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -34,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SysUiState
@@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
import dagger.Lazy
@@ -83,6 +85,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val sceneInteractor: SceneInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val bouncerInteractor: BouncerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val flags: SceneContainerFlags,
@@ -194,39 +197,41 @@ constructor(
}
}
applicationScope.launch {
- simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked ->
- val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
- val isUnlocked = deviceEntryInteractor.isUnlocked.value
-
- when {
- isAnySimLocked -> {
- switchToScene(
- targetSceneKey = Scenes.Bouncer,
- loggingReason = "Need to authenticate locked SIM card."
- )
- }
- isUnlocked && canSwipeToEnter == false -> {
- switchToScene(
- targetSceneKey = Scenes.Gone,
- loggingReason =
- "All SIM cards unlocked and device already" +
- " unlocked and lockscreen doesn't require a swipe to dismiss."
- )
- }
- else -> {
- switchToScene(
- targetSceneKey = Scenes.Lockscreen,
- loggingReason =
- "All SIM cards unlocked and device still locked" +
- " or lockscreen still requires a swipe to dismiss."
- )
+ simBouncerInteractor
+ .get()
+ .isAnySimSecure
+ .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair)
+ .collect { (isAnySimLocked, unlockStatus) ->
+ when {
+ isAnySimLocked -> {
+ switchToScene(
+ targetSceneKey = Scenes.Bouncer,
+ loggingReason = "Need to authenticate locked SIM card."
+ )
+ }
+ unlockStatus.isUnlocked &&
+ deviceEntryInteractor.canSwipeToEnter.value == false -> {
+ switchToScene(
+ targetSceneKey = Scenes.Gone,
+ loggingReason =
+ "All SIM cards unlocked and device already unlocked and " +
+ "lockscreen doesn't require a swipe to dismiss."
+ )
+ }
+ else -> {
+ switchToScene(
+ targetSceneKey = Scenes.Lockscreen,
+ loggingReason =
+ "All SIM cards unlocked and device still locked" +
+ " or lockscreen still requires a swipe to dismiss."
+ )
+ }
}
}
- }
}
applicationScope.launch {
- deviceEntryInteractor.isUnlocked
- .mapNotNull { isUnlocked ->
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .mapNotNull { deviceUnlockStatus ->
val renderedScenes =
when (val transitionState = sceneInteractor.transitionState.value) {
is ObservableTransitionState.Idle -> setOf(transitionState.scene)
@@ -238,7 +243,7 @@ constructor(
}
val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
val isOnBouncer = renderedScenes.contains(Scenes.Bouncer)
- if (!isUnlocked) {
+ if (!deviceUnlockStatus.isUnlocked) {
return@mapNotNull if (isOnLockscreen || isOnBouncer) {
// Already on lockscreen or bouncer, no need to change scenes.
null
@@ -250,8 +255,6 @@ constructor(
}
}
- val isBypassEnabled = deviceEntryInteractor.isBypassEnabled.value
- val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
when {
isOnBouncer ->
// When the device becomes unlocked in Bouncer, go to Gone.
@@ -266,14 +269,12 @@ constructor(
// when the unlock state changes indicates this is an active
// authentication attempt.
when {
- isBypassEnabled ->
- Scenes.Gone to
- "device has been unlocked on lockscreen with bypass" +
- " enabled"
- canSwipeToEnter == false ->
+ deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen ==
+ true ->
Scenes.Gone to
- "device has been unlocked on lockscreen using an active" +
- " authentication mechanism"
+ "device has been unlocked on lockscreen with bypass " +
+ "enabled or using an active authentication " +
+ "mechanism: ${deviceUnlockStatus.deviceUnlockSource}"
else -> null
}
// Not on lockscreen or bouncer, so remain in the current scene.
@@ -297,7 +298,7 @@ constructor(
)
} else {
val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
- val isUnlocked = deviceEntryInteractor.isUnlocked.value
+ val isUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
if (isUnlocked && canSwipeToEnter == false) {
val isTransitioningToLockscreen =
sceneInteractor.transitioningTo.value == Scenes.Lockscreen
@@ -429,8 +430,8 @@ constructor(
/** Keeps the interaction state of [CentralSurfaces] up-to-date. */
private fun hydrateInteractionState() {
applicationScope.launch {
- deviceEntryInteractor.isUnlocked
- .map { !it }
+ deviceUnlockedInteractor.deviceUnlockStatus
+ .map { !it.isUnlocked }
.flatMapLatest { isDeviceLocked ->
if (isDeviceLocked) {
sceneInteractor.transitionState
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 9e27dad0ea73..5664d59c7c48 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
import dagger.Module
import dagger.Provides
@@ -44,7 +45,8 @@ object SceneContainerFlag {
MigrateClocksToBlueprint.isEnabled &&
ComposeLockscreen.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
- KeyguardWmStateRefactor.isEnabled
+ KeyguardWmStateRefactor.isEnabled &&
+ PredictiveBackSysUiFlag.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
/** The main aconfig flag. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index a1481f6d6d2b..4cf18fb482d8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -34,9 +34,9 @@ import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -113,7 +113,7 @@ constructor(
override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
view.setChipIntents(imageData)
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
if (DEBUG_DISMISS) {
Log.d(TAG, "screenshot dismissal requested")
}
@@ -124,7 +124,7 @@ constructor(
}
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(event, 0, packageName) }
view.animateDismissal()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index bbf7ed529220..49144091cb62 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -165,6 +165,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
mParams.owner);
mImageData.subject = getSubjectString(mImageTime);
+ mImageData.imageTime = mImageTime;
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 5019a6fcaf8b..ca0a539d5ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -17,24 +17,37 @@
package com.android.systemui.screenshot
import android.app.ActivityOptions
+import android.app.BroadcastOptions
import android.app.ExitTransitionCoordinator
+import android.app.PendingIntent
import android.content.Context
import android.content.Intent
+import android.os.Process
+import android.os.UserHandle
+import android.provider.DeviceConfig
import android.util.Log
import android.util.Pair
import androidx.appcompat.content.res.AppCompatResources
import com.android.app.tracing.coroutines.launch
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.ActionIntentCreator.createEdit
import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
import com.android.systemui.screenshot.ScreenshotController.SavedImageData
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.text.DateFormat
+import java.util.Date
import kotlinx.coroutines.CoroutineScope
/**
@@ -48,7 +61,9 @@ interface ScreenshotActionsProvider {
interface Factory {
fun create(
request: ScreenshotData,
+ requestId: String,
windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
+ requestDismissal: () -> Unit,
): ScreenshotActionsProvider
}
}
@@ -59,9 +74,13 @@ constructor(
private val context: Context,
private val viewModel: ScreenshotViewModel,
private val actionExecutor: ActionIntentExecutor,
+ private val smartActionsProvider: SmartActionsProvider,
+ private val uiEventLogger: UiEventLogger,
@Application private val applicationScope: CoroutineScope,
@Assisted val request: ScreenshotData,
+ @Assisted val requestId: String,
@Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
+ @Assisted val requestDismissal: () -> Unit,
) : ScreenshotActionsProvider {
private var pendingAction: ((SavedImageData) -> Unit)? = null
private var result: SavedImageData? = null
@@ -70,6 +89,7 @@ constructor(
init {
viewModel.setPreviewAction {
debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
+ uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
startSharedTransition(createEdit(result.uri, context), true)
}
@@ -81,6 +101,7 @@ constructor(
context.resources.getString(R.string.screenshot_edit_description),
) {
debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
startSharedTransition(createEdit(result.uri, context), true)
}
@@ -93,11 +114,46 @@ constructor(
context.resources.getString(R.string.screenshot_share_description),
) {
debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+ uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
startSharedTransition(createShareWithSubject(result.uri, result.subject), false)
}
}
)
+ if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) {
+ smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
+ if (!quickShare.actionIntent.isImmutable) {
+ viewModel.addAction(
+ ActionButtonViewModel(
+ quickShare.getIcon().loadDrawable(context),
+ quickShare.title,
+ quickShare.title
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
+ onDeferrableActionTapped { result ->
+ uiEventLogger.log(
+ SCREENSHOT_SMART_ACTION_TAPPED,
+ 0,
+ request.packageNameString
+ )
+ sendPendingIntent(
+ smartActionsProvider
+ .wrapIntent(
+ quickShare,
+ result.uri,
+ result.subject,
+ requestId
+ )
+ .actionIntent
+ )
+ }
+ }
+ )
+ } else {
+ Log.w(TAG, "Received immutable quick share pending intent; ignoring")
+ }
+ }
+ }
}
override fun setCompletedScreenshot(result: SavedImageData) {
@@ -105,12 +161,30 @@ constructor(
Log.e(TAG, "Got a second completed screenshot for existing request!")
return
}
- if (result.uri == null || result.owner == null || result.subject == null) {
+ if (result.uri == null || result.owner == null || result.imageTime == null) {
Log.e(TAG, "Invalid result provided!")
return
}
+ if (result.subject == null) {
+ result.subject = getSubjectString(result.imageTime)
+ }
this.result = result
pendingAction?.invoke(result)
+ if (smartActionsEnabled(result.owner)) {
+ smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
+ viewModel.addActions(
+ smartActions.map {
+ ActionButtonViewModel(
+ it.getIcon().loadDrawable(context),
+ it.title,
+ it.title
+ ) {
+ sendPendingIntent(it.actionIntent)
+ }
+ }
+ )
+ }
+ }
}
override fun isPendingSharedTransition(): Boolean {
@@ -134,15 +208,47 @@ constructor(
}
}
+ private fun sendPendingIntent(pendingIntent: PendingIntent) {
+ try {
+ val options = BroadcastOptions.makeBasic()
+ options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ pendingIntent.send(options.toBundle())
+ requestDismissal.invoke()
+ } catch (e: PendingIntent.CanceledException) {
+ Log.e(TAG, "Intent cancelled", e)
+ }
+ }
+
+ private fun smartActionsEnabled(user: UserHandle): Boolean {
+ val savingToOtherUser = user != Process.myUserHandle()
+ return !savingToOtherUser &&
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true
+ )
+ }
+
+ private fun getSubjectString(imageTime: Long): String {
+ val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
+ return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
+ }
+
@AssistedFactory
interface Factory : ScreenshotActionsProvider.Factory {
override fun create(
request: ScreenshotData,
+ requestId: String,
windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
+ requestDismissal: () -> Unit,
): DefaultScreenshotActionsProvider
}
companion object {
private const val TAG = "ScreenshotActionsProvider"
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b43137fa2a74..70d1129a2b40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -96,7 +96,10 @@ import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import kotlin.Unit;
+
import java.util.List;
+import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -167,6 +170,7 @@ public class ScreenshotController {
public Notification.Action quickShareAction;
public UserHandle owner;
public String subject; // Title for sharing
+ public Long imageTime; // Time at which screenshot was saved
/**
* Used to reset the return data on error
@@ -176,6 +180,7 @@ public class ScreenshotController {
smartActions = null;
quickShareAction = null;
subject = null;
+ imageTime = null;
}
}
@@ -261,11 +266,9 @@ public class ScreenshotController {
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
private boolean mBlockAttach;
-
- private ScreenshotActionsProvider mActionsProvider;
-
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
+ private ScreenshotActionsProvider mActionsProvider;
private String mPackageName = "";
private final BroadcastReceiver mCopyBroadcastReceiver;
@@ -317,6 +320,7 @@ public class ScreenshotController {
@Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
+ mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
@@ -347,7 +351,6 @@ public class ScreenshotController {
mAssistContentRequester = assistContentRequester;
mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
- mActionsProviderFactory = actionsProviderFactory;
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -441,8 +444,19 @@ public class ScreenshotController {
return;
}
- saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
- this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ if (screenshotShelfUi()) {
+ final UUID requestId = UUID.randomUUID();
+ final String screenshotId = String.format("Screenshot_%s", requestId);
+ mActionsProvider = mActionsProviderFactory.create(screenshot, screenshotId,
+ this::createWindowTransition, () -> {
+ mViewProxy.requestDismissal(null);
+ return Unit.INSTANCE;
+ });
+ saveScreenshotInBackground(screenshot, requestId, finisher);
+ } else {
+ saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
+ this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ }
// The window is focusable by default
setWindowFocusable(true);
@@ -477,7 +491,9 @@ public class ScreenshotController {
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- mScreenshotHandler.cancelTimeout(); // restarted after animation
+ if (!screenshotShelfUi()) {
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
+ }
}
private boolean shouldShowUi() {
@@ -497,11 +513,6 @@ public class ScreenshotController {
mViewProxy.reset();
- if (screenshotShelfUi()) {
- mActionsProvider =
- mActionsProviderFactory.create(screenshot, this::createWindowTransition);
- }
-
if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (!mViewProxy.isDismissing()) {
@@ -921,6 +932,39 @@ public class ScreenshotController {
mScreenshotHandler.cancelTimeout();
}
+ private void saveScreenshotInBackground(
+ ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
+ ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
+ requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId);
+ future.addListener(() -> {
+ try {
+ ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
+ logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
+ mScreenshotHandler.resetTimeout();
+ if (result.uri != null) {
+ final SavedImageData savedImageData = new SavedImageData();
+ savedImageData.uri = result.uri;
+ savedImageData.owner = screenshot.getUserHandle();
+ savedImageData.imageTime = result.timestamp;
+ mActionsProvider.setCompletedScreenshot(savedImageData);
+ mViewProxy.setChipIntents(savedImageData);
+ }
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ + "finisher.accept(\"" + result.uri + "\"");
+ }
+ finisher.accept(result.uri);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to store screenshot", e);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
+ }
+ finisher.accept(null);
+ }
+ }, mMainExecutor);
+ }
+
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
@@ -958,11 +1002,6 @@ public class ScreenshotController {
logSuccessOnActionsReady(imageData);
mScreenshotHandler.resetTimeout();
- if (screenshotShelfUi()) {
- mActionsProvider.setCompletedScreenshot(imageData);
- return;
- }
-
if (imageData.uri != null) {
if (DEBUG_UI) {
Log.d(TAG, "Showing UI actions");
@@ -1014,20 +1053,27 @@ public class ScreenshotController {
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
- private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
- if (imageData.uri == null) {
+ private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
+ if (uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
- if (mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ if (mUserManager.isManagedProfile(owner.getIdentifier())) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
mPackageName);
}
}
}
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ logScreenshotResultStatus(imageData.uri, imageData.owner);
+ }
+
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index defddc30586e..6b9332b39816 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -98,7 +98,7 @@ constructor(
override fun setChipIntents(imageData: SavedImageData) {}
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }
// If we're already animating out, don't restart the animation
@@ -106,7 +106,7 @@ constructor(
debugLog(DEBUG_DISMISS) { "Already dismissing, ignoring duplicate command $event" }
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(it, 0, packageName) }
val animator = animationController.getExitAnimation()
animator.addListener(
object : AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 6be32a97f4e2..a4069d11f8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -46,7 +46,7 @@ interface ScreenshotViewProxy {
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun requestDismissal(event: ScreenshotEvent)
+ fun requestDismissal(event: ScreenshotEvent?)
fun showScrollChip(packageName: String, onClick: Runnable)
fun hideScrollChip()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
new file mode 100644
index 000000000000..2eaff8654a9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.os.Process
+import android.os.SystemClock
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import javax.inject.Inject
+import kotlin.random.Random
+
+/**
+ * Handle requesting smart/quickshare actions from the provider and executing an action when the
+ * action futures complete.
+ */
+class SmartActionsProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val smartActions: ScreenshotNotificationSmartActionsProvider,
+) {
+ /**
+ * Requests quick share action for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param onAction callback to run when quick share action is returned
+ */
+ fun requestQuickShare(
+ data: ScreenshotData,
+ id: String,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val user = data.userHandle ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestQuickShareAction(id, bitmap, component, user) { quickShareAction ->
+ onAction(quickShareAction)
+ }
+ }
+
+ /**
+ * Requests smart actions for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param result the data for the saved image
+ * @param onActions callback to run when actions are returned
+ */
+ fun requestSmartActions(
+ data: ScreenshotData,
+ id: String,
+ result: ScreenshotController.SavedImageData,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val user = data.userHandle ?: return
+ val uri = result.uri ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions ->
+ onActions(actions)
+ }
+ }
+
+ /**
+ * Wraps the given quick share action in a broadcast intent.
+ *
+ * @param quickShare the quick share action to wrap
+ * @param uri the URI of the saved screenshot
+ * @param subject the subject/title for the screenshot
+ * @param id the request ID of the screenshot
+ * @return the wrapped action
+ */
+ fun wrapIntent(
+ quickShare: Notification.Action,
+ uri: Uri,
+ subject: String,
+ id: String
+ ): Notification.Action {
+ val wrappedIntent: Intent =
+ Intent(context, SmartActionsReceiver::class.java)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(
+ ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, subject)
+ )
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ val extras: Bundle = quickShare.extras
+ val actionType =
+ extras.getString(
+ ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+ ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE
+ )
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ wrappedIntent
+ .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(ScreenshotController.EXTRA_ID, id)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
+ val broadcastIntent =
+ PendingIntent.getBroadcast(
+ context,
+ Random.nextInt(),
+ wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ return Notification.Action.Builder(quickShare.getIcon(), quickShare.title, broadcastIntent)
+ .setContextual(true)
+ .addExtras(extras)
+ .build()
+ }
+
+ private fun createFillInIntent(uri: Uri, subject: String): Intent {
+ val fillIn = Intent()
+ fillIn.setType("image/png")
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri)
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, subject)
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ val clipData =
+ ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri))
+ fillIn.clipData = clipData
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ return fillIn
+ }
+
+ private fun requestQuickShareAction(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ timeoutMs: Long = 500,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) {
+ it.firstOrNull()?.let { action -> onAction(action) }
+ }
+ }
+
+ private fun requestSmartActions(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ uri: Uri?,
+ actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType,
+ timeoutMs: Long = 500,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val enabled = isSmartActionsEnabled(user)
+ debugLog(DEBUG_ACTIONS) {
+ ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " +
+ "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user")
+ }
+ if (!enabled) {
+ debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" }
+ onActions(listOf())
+ return
+ }
+ if (image.config != Bitmap.Config.HARDWARE) {
+ debugLog(DEBUG_ACTIONS) {
+ "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list."
+ }
+ onActions(listOf())
+ return
+ }
+ var smartActionsFuture: CompletableFuture<List<Notification.Action>>
+ val startTimeMs = SystemClock.uptimeMillis()
+ try {
+ smartActionsFuture =
+ smartActions.getActions(id, uri, image, component, actionType, user)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Failed to get future for screenshot notification smart actions."
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs
+ )
+ onActions(listOf())
+ return
+ }
+ try {
+ val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS) {
+ ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " +
+ "actionType=$actionType")
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs
+ )
+ onActions(actions)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType"
+ }
+ val status =
+ if (e is TimeoutException) {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ } else {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status,
+ waitTimeMs
+ )
+ onActions(listOf())
+ }
+ }
+
+ private fun notifyScreenshotOp(
+ screenshotId: String,
+ op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp,
+ status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus,
+ durationMs: Long
+ ) {
+ debugLog(DEBUG_ACTIONS) {
+ "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs"
+ }
+ try {
+ smartActions.notifyOp(screenshotId, op, status, durationMs)
+ } catch (e: Throwable) {
+ Log.e(TAG, "Error in notifyScreenshotOp: ", e)
+ }
+ }
+ private fun isSmartActionsEnabled(user: UserHandle): Boolean {
+ // Smart actions don't yet work for cross-user saves.
+ val savingToOtherUser = user !== Process.myUserHandle()
+ val actionsEnabled =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true
+ )
+ return !savingToOtherUser && actionsEnabled
+ }
+
+ companion object {
+ private const val TAG = "SmartActionsProvider"
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index ea05884096c8..b191a1a52616 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot.ui.binder
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 343f37744ce9..dcfd47b7b303 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3174,6 +3174,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
notifyExpandingFinished();
}
+ // TODO(b/332732878): replace this call when scene container is enabled
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index c5e07e818b6b..ebebbe65d54b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.ShadeTouchLog
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -61,6 +62,7 @@ constructor(
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val notificationStackScrollLayout: NotificationStackScrollLayout,
@ShadeTouchLog private val touchLog: LogBuffer,
private val vibratorHelper: VibratorHelper,
@@ -148,7 +150,11 @@ constructor(
}
private fun getCollapseDestinationScene(): SceneKey {
- return if (deviceEntryInteractor.isDeviceEntered.value) {
+ // Always check whether device is unlocked before transitioning to gone scene.
+ return if (
+ deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked &&
+ deviceEntryInteractor.isDeviceEntered.value
+ ) {
Scenes.Gone
} else {
Scenes.Lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
index adb29287e40e..ec4018c7d238 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -46,7 +46,7 @@ class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewController
}
override fun setTouchAndAnimationDisabled(disabled: Boolean) {
- // TODO(b/322197941): determine if still needed
+ // TODO(b/332732878): determine if still needed
}
override fun setWillPlayDelayedDozeAmountAnimation(willPlay: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index d6858cad6d0b..78e108d444d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Pair;
@@ -57,6 +58,7 @@ import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
@@ -104,6 +106,7 @@ public final class KeyboardShortcutListSearch {
private WindowManager mWindowManager;
private EditText mSearchEditText;
+ private ImageButton mEditTextCancel;
private String mQueryString;
private int mCurrentCategoryIndex = 0;
private Map<Integer, Boolean> mKeySearchResultMap = new HashMap<>();
@@ -143,7 +146,7 @@ public final class KeyboardShortcutListSearch {
@VisibleForTesting
KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
this.mContext = new ContextThemeWrapper(
- context, android.R.style.Theme_DeviceDefault_Settings);
+ context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
if (windowManager != null) {
this.mWindowManager = windowManager;
@@ -853,13 +856,14 @@ public final class KeyboardShortcutListSearch {
List<List<KeyboardShortcutMultiMappingGroup>> keyboardShortcutMultiMappingGroupList) {
mQueryString = null;
LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
- mKeyboardShortcutsBottomSheetDialog =
- new BottomSheetDialog(mContext);
+ mKeyboardShortcutsBottomSheetDialog = new BottomSheetDialog(mContext);
final View keyboardShortcutsView = inflater.inflate(
R.layout.keyboard_shortcuts_search_view, null);
LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById(
R.id.keyboard_shortcuts_container);
mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
+ Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
+ setWindowProperties(keyboardShortcutsWindow);
mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
setButtonsDefaultStatus(keyboardShortcutsView);
populateCurrentAppButton();
@@ -874,25 +878,11 @@ public final class KeyboardShortcutListSearch {
}
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+ behavior.setDraggable(true);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setSkipCollapsed(true);
- behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
- @Override
- public void onStateChanged(@NonNull View bottomSheet, int newState) {
- if (newState == BottomSheetBehavior.STATE_DRAGGING) {
- behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
- }
- }
- @Override
- public void onSlide(@NonNull View bottomSheet, float slideOffset) {
- // Do nothing.
- }
- });
- mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
- Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
- keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
synchronized (sLock) {
// show KeyboardShortcutsBottomSheetDialog only if it has not been dismissed already
if (sInstance != null) {
@@ -908,6 +898,8 @@ public final class KeyboardShortcutListSearch {
}
}
mSearchEditText = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search);
+ mEditTextCancel = keyboardShortcutsView.findViewById(
+ R.id.keyboard_shortcuts_search_cancel);
mSearchEditText.addTextChangedListener(
new TextWatcher() {
@Override
@@ -921,6 +913,8 @@ public final class KeyboardShortcutListSearch {
shortcutsContainer.setAccessibilityPaneTitle(mContext.getString(
R.string.keyboard_shortcut_a11y_show_search_results));
}
+ mEditTextCancel.setVisibility(
+ TextUtils.isEmpty(mQueryString) ? View.GONE : View.VISIBLE);
}
@Override
@@ -933,9 +927,28 @@ public final class KeyboardShortcutListSearch {
// Do nothing.
}
});
- ImageButton editTextCancel = keyboardShortcutsView.findViewById(
- R.id.keyboard_shortcuts_search_cancel);
- editTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+
+ mEditTextCancel.setOnClickListener(v -> mSearchEditText.setText(null));
+ }
+
+ private static void setWindowProperties(Window keyboardShortcutsWindow) {
+ keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.copyFrom(keyboardShortcutsWindow.getAttributes());
+ // Allows the bottom sheet dialog to render all the way to the bottom of the screen,
+ // behind the gesture navigation bar.
+ params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ params.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ keyboardShortcutsWindow.setAttributes(params);
+ keyboardShortcutsWindow.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
+ int bottom = insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
+ View container = v.findViewById(R.id.keyboard_shortcuts_container);
+ container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
+ container.getPaddingRight(), bottom);
+ return WindowInsets.CONSUMED;
+ });
+ keyboardShortcutsWindow.setWindowAnimations(
+ R.style.KeyboardShortcutHelper_BottomSheetDialogAnimation);
}
private void populateKeyboardShortcutSearchList(LinearLayout keyboardShortcutsLayout) {
@@ -1256,10 +1269,10 @@ public final class KeyboardShortcutListSearch {
if (mContext.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT) {
lp.width = (int) (display.getWidth() * 0.8);
- lp.height = (int) (display.getHeight() * 0.7);
+ lp.height = (int) (display.getHeight() * 0.8);
} else {
lp.width = (int) (display.getWidth() * 0.7);
- lp.height = (int) (display.getHeight() * 0.8);
+ lp.height = (int) (display.getHeight() * 0.95);
}
window.setGravity(Gravity.BOTTOM);
window.setAttributes(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 2539372d49fd..aa6bec1f06f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -46,6 +46,7 @@ import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -198,7 +199,7 @@ public class StatusBarStateControllerImpl implements
if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
combineFlows(
- mDeviceUnlockedInteractorLazy.get().isDeviceUnlocked(),
+ mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
mSceneInteractorLazy.get().getCurrentScene(),
this::calculateStateFromSceneFramework),
this::onStatusBarStateChanged);
@@ -646,11 +647,11 @@ public class StatusBarStateControllerImpl implements
}
private int calculateStateFromSceneFramework(
- boolean isDeviceUnlocked,
+ DeviceUnlockStatus deviceUnlockStatus,
SceneKey currentScene) {
SceneContainerFlag.isUnexpectedlyInLegacyMode();
- if (isDeviceUnlocked) {
+ if (deviceUnlockStatus.isUnlocked()) {
return StatusBarState.SHADE;
} else {
return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 8531eaa46804..1a223c110ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.os.SystemProperties
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -340,12 +341,41 @@ constructor(
var hasFilteredAnyNotifs = false
+ /**
+ * the [notificationMinimalismPrototype] will now show seen notifications on the locked
+ * shade by default, but this property read allows that to be quickly disabled for
+ * testing
+ */
+ private val minimalismShowOnLockedShade
+ get() =
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.show_on_locked_shade",
+ true
+ )
+
+ /**
+ * Encapsulates a definition of "being on the keyguard". Note that these two definitions
+ * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
+ * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
+ * is any state where the keyguard has not been dismissed, including locked shade and
+ * occluded lock screen.
+ *
+ * Returning false for locked shade and occluded states means that this filter will
+ * allow seen notifications to appear in the locked shade.
+ */
+ private fun isOnKeyguard(): Boolean =
+ if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) {
+ statusBarStateController.state == StatusBarState.KEYGUARD
+ } else {
+ keyguardRepository.isKeyguardShowing()
+ }
+
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
when {
// Don't apply filter if the setting is disabled
!unseenFilterEnabled -> false
// Don't apply filter if the keyguard isn't currently showing
- !keyguardRepository.isKeyguardShowing() -> false
+ !isOnKeyguard() -> false
// Don't apply the filter if the notification is unseen
unseenNotifications.contains(entry) -> false
// Don't apply the filter to (non-promoted) group summaries
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
new file mode 100644
index 000000000000..0344b32dd6ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the heads-up cycling flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationHeadsUpCycling {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationContentAlphaOptimization()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3367dc427f43..6a7a5cd8f6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -230,7 +230,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateAnimator mStateAnimator;
- private boolean mAnimationsEnabled;
+ // TODO(b/332732878): call setAnimationsEnabled with scene container enabled, then remove this
+ private boolean mAnimationsEnabled = SceneContainerFlag.isEnabled();
private boolean mChangePositionInProgress;
private boolean mChildTransferInProgress;
@@ -2904,6 +2905,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
public void setAnimationsEnabled(boolean animationsEnabled) {
+ // TODO(b/332732878): remove the initial value of this field once the setter is called
mAnimationsEnabled = animationsEnabled;
updateNotificationAnimationStates();
if (!animationsEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 1b53cbed8354..5bd4c758d678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -17,14 +17,15 @@
package com.android.systemui.statusbar.notification.stack
import android.content.res.Resources
+import android.os.SystemProperties
import android.util.Log
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
import com.android.systemui.Flags.notificationMinimalismPrototype
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -177,8 +178,8 @@ constructor(
// TODO: Avoid making this split shade assumption by simply checking the stack for media
val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
- val isMediaShowingInStack = isMediaShowing && !splitShadeStateController
- .shouldUseSplitNotificationShade(resources)
+ val isMediaShowingInStack =
+ isMediaShowing && !splitShadeStateController.shouldUseSplitNotificationShade(resources)
log { "\tGet maxNotifWithoutSavingSpace ---" }
val maxNotifWithoutSavingSpace =
@@ -378,8 +379,17 @@ constructor(
}
fun updateResources() {
- maxKeyguardNotifications = if (notificationMinimalismPrototype()) 1
- else infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
+ maxKeyguardNotifications =
+ infiniteIfNegative(
+ if (notificationMinimalismPrototype()) {
+ SystemProperties.getInt(
+ "persist.notification_minimalism_prototype.lock_screen_max_notifs",
+ 1
+ )
+ } else {
+ resources.getInteger(R.integer.keyguard_max_notification_count)
+ }
+ )
maxNotificationsExcludesMedia = notificationMinimalismPrototype()
dividerHeight =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 2798dbfc62e4..5baf6a069602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,7 +28,6 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.newAodTransition;
-import static com.android.systemui.Flags.predictiveBackSysui;
import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -837,7 +836,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
mLightRevealScrim = lightRevealScrim;
- if (predictiveBackSysui()) {
+ if (PredictiveBackSysUiFlag.isEnabled()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
}
@@ -3061,7 +3060,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
public void onConfigChanged(Configuration newConfig) {
updateResources();
updateDisplaySize(); // populates mDisplayMetrics
- if (predictiveBackSysui()) {
+ if (PredictiveBackSysUiFlag.isEnabled()) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt
new file mode 100644
index 000000000000..74d6ba57a8ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the predictive back flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PredictiveBackSysUiFlag {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_PREDICTIVE_BACK_SYSUI
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.predictiveBackSysui()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 55a0f59bf461..9cdecef3f6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -17,9 +17,12 @@ package com.android.systemui.statusbar.policy
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import java.io.PrintWriter
import javax.inject.Inject
/*
@@ -27,7 +30,9 @@ import javax.inject.Inject
* succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
*/
@SysUISingleton
-class AvalancheController @Inject constructor() {
+class AvalancheController @Inject constructor(
+ dumpManager: DumpManager,
+) : Dumpable {
private val tag = "AvalancheController"
private val debug = false
@@ -54,22 +59,26 @@ class AvalancheController @Inject constructor() {
// For debugging only
@VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
- /**
- * Run or delay Runnable for given HeadsUpEntry
- */
- fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ init {
+ dumpManager.registerNormalDumpable(tag, /* module */ this)
+ }
+
+ /** Run or delay Runnable for given HeadsUpEntry */
+ fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
if (!NotificationThrottleHun.isEnabled) {
runnable.run()
return
}
val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
-
+ if (entry == null) {
+ log { "Entry is NULL, stop update." }
+ return;
+ }
if (debug) {
debugRunnableLabelMap[runnable] = label
}
-
if (isShowing(entry)) {
- log {"$fn => [update showing]" }
+ log { "$fn => [update showing]" }
runnable.run()
} else if (entry in nextMap) {
log { "$fn => [update next]" }
@@ -164,9 +173,7 @@ class AvalancheController @Inject constructor() {
}
}
- /**
- * Return true if entry is waiting to show.
- */
+ /** Return true if entry is waiting to show. */
fun isWaiting(key: String): Boolean {
if (!NotificationThrottleHun.isEnabled) {
return false
@@ -179,9 +186,7 @@ class AvalancheController @Inject constructor() {
return false
}
- /**
- * Return list of keys for huns waiting
- */
+ /** Return list of keys for huns waiting */
fun getWaitingKeys(): MutableList<String> {
if (!NotificationThrottleHun.isEnabled) {
return mutableListOf()
@@ -254,12 +259,15 @@ class AvalancheController @Inject constructor() {
}
}
- // TODO(b/315362456) expose as dumpable for bugreports
+ private fun getStateStr(): String {
+ return "SHOWING: ${getKey(headsUpEntryShowing)}" +
+ "\tNEXT LIST: $nextListStr\tMAP: $nextMapStr" +
+ "\tDROP: $dropSetStr"
+ }
+
private fun logState(reason: String) {
- log { "state $reason" }
- log { "showing: " + getKey(headsUpEntryShowing) }
- log { "next list: $nextListStr map: $nextMapStr" }
- log { "drop: $dropSetStr" }
+ log { "REASON $reason" }
+ log { getStateStr() }
}
private val dropSetStr: String
@@ -298,4 +306,8 @@ class AvalancheController @Inject constructor() {
}
return entry.mEntry!!.key
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("AvalancheController: ${getStateStr()}")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 20a82a403eb7..d99af2ddb95d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -165,6 +165,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void showNotification(@NonNull NotificationEntry entry) {
HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry);
+ mLogger.logShowNotificationRequest(entry);
+
Runnable runnable = () -> {
// TODO(b/315362456) log outside runnable too
mLogger.logShowNotification(entry);
@@ -219,6 +221,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null);
+
Runnable runnable = () -> {
updateNotificationInternal(key, shouldHeadsUpAgain);
};
@@ -378,8 +382,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logRemoveEntryRequest(key);
Runnable runnable = () -> {
+ mLogger.logRemoveEntry(key);
+
if (headsUpEntry == null) {
return;
}
@@ -566,8 +573,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-
+ mLogger.logUnpinEntryRequest(key);
Runnable runnable = () -> {
+ mLogger.logUnpinEntry(key);
+
setEntryPinned(headsUpEntry, false /* isPinned */);
// maybe it got un sticky
headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
@@ -886,6 +895,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
* Clear any pending removal runnables.
*/
public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ mLogger.logAutoRemoveCancelRequest(this.mEntry, reason);
Runnable runnable = () -> {
final boolean removed = cancelAutoRemovalCallbackInternal();
@@ -900,6 +910,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
@NonNull String reason) {
+ mLogger.logAutoRemoveRequest(this.mEntry, reason);
Runnable runnable = () -> {
long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index f6154afec273..a30660645990 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -58,6 +58,14 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logShowNotificationRequest(entry: NotificationEntry) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ }, {
+ "request: show notification $str1"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -76,6 +84,15 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "request: reschedule auto remove of $str1 reason: $str2"
+ })
+ }
+
fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -86,6 +103,15 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) {
+ buffer.log(TAG, INFO, {
+ str1 = entry.logKey
+ str2 = reason ?: "unknown"
+ }, {
+ "request: cancel auto remove of $str1 reason: $str2"
+ })
+ }
+
fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
@@ -95,6 +121,38 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logRemoveEntryRequest(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "request: remove entry $str1"
+ })
+ }
+
+ fun logRemoveEntry(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "remove entry $str1"
+ })
+ }
+
+ fun logUnpinEntryRequest(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "request: unpin entry $str1"
+ })
+ }
+
+ fun logUnpinEntry(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ }, {
+ "unpin entry $str1"
+ })
+ }
+
fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
@@ -112,6 +170,16 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
+ fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
+ buffer.log(TAG, INFO, {
+ str1 = logKey(key)
+ bool1 = alert
+ bool2 = hasEntry
+ }, {
+ "request: update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2"
+ })
+ }
+
fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
buffer.log(TAG, INFO, {
str1 = logKey(key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 988564ae907c..db4e605dc9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -14,7 +14,12 @@
package com.android.systemui.statusbar.policy
-import android.os.UserManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.os.UserManager.DISALLOW_CAMERA_TOGGLE
+import android.os.UserManager.DISALLOW_CONFIG_LOCATION
+import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE
+import android.os.UserManager.DISALLOW_SHARE_LOCATION
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -39,6 +44,11 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources
+import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyToggleTileMapper
import com.android.systemui.qs.tiles.impl.uimodenight.domain.UiModeNightTileMapper
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor
@@ -76,6 +86,8 @@ interface PolicyModule {
const val ALARM_TILE_SPEC = "alarm"
const val UIMODENIGHT_TILE_SPEC = "dark"
const val WORK_MODE_TILE_SPEC = "work"
+ const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
+ const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
/** Inject flashlight config */
@Provides
@@ -125,8 +137,8 @@ interface PolicyModule {
policy =
QSTilePolicy.Restricted(
listOf(
- UserManager.DISALLOW_SHARE_LOCATION,
- UserManager.DISALLOW_CONFIG_LOCATION
+ DISALLOW_SHARE_LOCATION,
+ DISALLOW_CONFIG_LOCATION
)
)
)
@@ -243,6 +255,72 @@ interface PolicyModule {
stateInteractor,
mapper,
)
+
+ /** Inject camera toggle config */
+ @Provides
+ @IntoMap
+ @StringKey(CAMERA_TOGGLE_TILE_SPEC)
+ fun provideCameraToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(CAMERA_TOGGLE_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_camera_access_icon_off,
+ labelRes = R.string.quick_settings_camera_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ policy = QSTilePolicy.Restricted(listOf(DISALLOW_CAMERA_TOGGLE)),
+ )
+
+ /** Inject camera toggle tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(CAMERA_TOGGLE_TILE_SPEC)
+ fun provideCameraToggleTileViewModel(
+ factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>,
+ mapper: SensorPrivacyToggleTileMapper.Factory,
+ stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory,
+ userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory,
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(CAMERA_TOGGLE_TILE_SPEC),
+ userActionInteractor.create(CAMERA),
+ stateInteractor.create(CAMERA),
+ mapper.create(SensorPrivacyTileResources.CameraPrivacyTileResources),
+ )
+
+ /** Inject microphone toggle config */
+ @Provides
+ @IntoMap
+ @StringKey(MIC_TOGGLE_TILE_SPEC)
+ fun provideMicrophoneToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(MIC_TOGGLE_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_mic_access_off,
+ labelRes = R.string.quick_settings_mic_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ policy = QSTilePolicy.Restricted(listOf(DISALLOW_MICROPHONE_TOGGLE)),
+ )
+
+ /** Inject microphone toggle tile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(MIC_TOGGLE_TILE_SPEC)
+ fun provideMicrophoneToggleTileViewModel(
+ factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>,
+ mapper: SensorPrivacyToggleTileMapper.Factory,
+ stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory,
+ userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory,
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(MIC_TOGGLE_TILE_SPEC),
+ userActionInteractor.create(MICROPHONE),
+ stateInteractor.create(MICROPHONE),
+ mapper.create(SensorPrivacyTileResources.MicrophonePrivacyTileResources),
+ )
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
index 860068c137a3..0d53277e51dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -20,6 +20,7 @@ import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static com.android.server.notification.Flags.screenshareNotificationHiding;
+import static com.android.systemui.Flags.screenshareNotificationHidingBugFix;
import android.annotation.MainThread;
import android.app.IActivityManager;
@@ -31,6 +32,7 @@ import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.RemoteException;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
@@ -316,6 +318,10 @@ public class SensitiveNotificationProtectionControllerImpl
return false;
}
+ if (screenshareNotificationHidingBugFix() && UserHandle.isCore(sbn.getUid())) {
+ return false; // do not hide/redact notifications from system uid
+ }
+
// Only protect/redact notifications if the developer has not explicitly set notification
// visibility as public and users has not adjusted default channel visibility to private
boolean notificationRequestsRedaction = entry.isNotificationVisibilityPrivate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 9b8cf592d496..6a0462b72544 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -122,7 +122,6 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
fingerprintPropertyRepository.supportsUdfps()
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
deviceEntryFingerprintAuthRepository.setIsRunning(true)
- deviceEntryRepository.setUnlocked(false)
// Lockscreen
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 1c6f25147a1e..a127631a536d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -32,6 +32,7 @@ import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.GestureDetector;
+import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.MotionEvent;
@@ -83,11 +84,14 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
private final GestureDetector.OnGestureListener mGestureListener;
private final DisplayHelper mDisplayHelper;
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
private final Rect mDisplayBounds = Mockito.mock(Rect.class);
+ private final IWindowManager mIWindowManager;
Environment(Set<DreamTouchHandler> handlers) {
mLifecycle = Mockito.mock(Lifecycle.class);
mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
+ mIWindowManager = Mockito.mock(IWindowManager.class);
mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
@@ -100,8 +104,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
mDisplayHelper = Mockito.mock(DisplayHelper.class);
when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
.thenReturn(mDisplayBounds);
- mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory,
- mDisplayHelper, handlers);
+ mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+ mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
mMonitor.init();
final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
@@ -163,7 +167,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
environment.publishInputEvent(initialEvent);
// Verify display bounds passed into TouchHandler#getTouchInitiationRegion
- verify(touchHandler).getTouchInitiationRegion(eq(environment.getDisplayBounds()), any());
+ verify(touchHandler).getTouchInitiationRegion(
+ eq(environment.getDisplayBounds()), any(), any());
final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -182,7 +187,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
return null;
- }).when(touchHandler).getTouchInitiationRegion(any(), any());
+ }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -211,7 +216,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
return null;
- }).when(touchHandler).getTouchInitiationRegion(any(), any());
+ }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -264,7 +269,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
// Make sure there is no active session.
verify(touchHandler, never()).onSessionStart(any());
- verify(touchHandler, never()).getTouchInitiationRegion(any(), any());
+ verify(touchHandler, never()).getTouchInitiationRegion(any(), any(), any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
deleted file mode 100644
index e044eeca8303..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.mediaprojection.permission
-
-import android.app.AlertDialog
-import android.media.projection.MediaProjectionConfig
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.WindowManager
-import android.widget.Spinner
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
-
- private lateinit var dialog: AlertDialog
-
- private val flags = mock<FeatureFlagsClassic>()
- private val onStartRecordingClicked = mock<Runnable>()
- private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
-
- private val mediaProjectionConfig: MediaProjectionConfig =
- MediaProjectionConfig.createConfigForDefaultDisplay()
- private val appName: String = "testApp"
- private val hostUid: Int = 12345
-
- private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
- private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
- private val resIdSingleAppDisabled =
- R.string.media_projection_entry_app_permission_dialog_single_app_disabled
-
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
- @After
- fun teardown() {
- if (::dialog.isInitialized) {
- dialog.dismiss()
- }
- }
-
- @Test
- fun showDialog_forceShowPartialScreenShareFalse() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = false
- val overrideDisableSingleAppOption = false
- setUpAndShowDialog(overrideDisableSingleAppOption)
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
- val secondOptionText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
-
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
- }
-
- @Test
- fun showDialog_forceShowPartialScreenShareTrue() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = true
- val overrideDisableSingleAppOption = true
- setUpAndShowDialog(overrideDisableSingleAppOption)
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
- val secondOptionText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text1)
- ?.text
-
- // check that the first option is single app and enabled
- assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
-
- // check that the second option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), secondOptionText)
- }
-
- private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
- val delegate =
- MediaProjectionPermissionDialogDelegate(
- context,
- mediaProjectionConfig,
- {},
- onStartRecordingClicked,
- appName,
- overrideDisableSingleAppOption,
- hostUid,
- mediaProjectionMetricsLogger
- )
-
- dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
- SystemUIDialog.applyFlags(dialog)
- SystemUIDialog.setDialogSize(dialog)
-
- dialog.window?.addSystemFlags(
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
- )
-
- delegate.onCreate(dialog, savedInstanceState = null)
- dialog.show()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index f49c6e25c2d3..4a5cf57e6fa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -18,13 +18,18 @@ package com.android.systemui.screenshot
import android.app.ActivityOptions
import android.app.ExitTransitionCoordinator
+import android.app.Notification
+import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.clipboardoverlay.EditTextActivity
+import com.android.systemui.res.R
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -41,7 +46,11 @@ import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyBlocking
+import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -52,15 +61,19 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
private val actionIntentExecutor = mock<ActionIntentExecutor>()
private val accessibilityManager = mock<AccessibilityManager>()
+ private val uiEventLogger = mock<UiEventLogger>()
+ private val smartActionsProvider = mock<SmartActionsProvider>()
private val transition = mock<android.util.Pair<ActivityOptions, ExitTransitionCoordinator>>()
+ private val requestDismissal = mock<() -> Unit>()
private val request = ScreenshotData.forTesting()
private val invalidResult = ScreenshotController.SavedImageData()
private val validResult =
ScreenshotController.SavedImageData().apply {
uri = Uri.EMPTY
- owner = UserHandle.CURRENT
+ owner = UserHandle.OWNER
subject = "Test"
+ imageTime = 0
}
private lateinit var viewModel: ScreenshotViewModel
@@ -69,20 +82,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
@Before
fun setUp() {
viewModel = ScreenshotViewModel(accessibilityManager)
- actionsProvider =
- DefaultScreenshotActionsProvider(
- context,
- viewModel,
- actionIntentExecutor,
- testScope,
- request
- ) {
- transition
- }
+ request.userHandle = UserHandle.OWNER
}
@Test
fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
assertNotNull(viewModel.previewAction.value)
viewModel.previewAction.value!!.invoke()
verifyNoMoreInteractions(actionIntentExecutor)
@@ -90,6 +96,8 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
@Test
fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
assertThat(viewModel.actions.value.size).isEqualTo(2)
val firstAction = viewModel.actions.value[0]
assertThat(firstAction.onClicked).isNotNull()
@@ -102,6 +110,8 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
@Test
fun actionAccessed_withInvalidResult_doesNothing() {
+ actionsProvider = createActionsProvider()
+
actionsProvider.setCompletedScreenshot(invalidResult)
viewModel.previewAction.value!!.invoke()
viewModel.actions.value[1].onClicked!!.invoke()
@@ -112,10 +122,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
@Test
@Ignore("b/332526567")
fun actionAccessed_withResult_launchesIntent() = runTest {
+ actionsProvider = createActionsProvider()
+
actionsProvider.setCompletedScreenshot(validResult)
viewModel.actions.value[0].onClicked!!.invoke()
scheduler.advanceUntilIdle()
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
verifyBlocking(actionIntentExecutor) {
launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(true))
@@ -126,16 +139,65 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
@Test
@Ignore("b/332526567")
fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
+ actionsProvider = createActionsProvider()
+
viewModel.actions.value[0].onClicked!!.invoke()
viewModel.previewAction.value!!.invoke()
viewModel.actions.value[1].onClicked!!.invoke()
actionsProvider.setCompletedScreenshot(validResult)
scheduler.advanceUntilIdle()
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
verifyBlocking(actionIntentExecutor) {
launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(false))
}
assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
}
+
+ @Test
+ fun quickShareTapped_wrapsAndSendsIntent() = runTest {
+ val quickShare =
+ Notification.Action(
+ R.drawable.ic_screenshot_edit,
+ "TestQuickShare",
+ PendingIntent.getActivity(
+ context,
+ 0,
+ Intent(context, EditTextActivity::class.java),
+ PendingIntent.FLAG_MUTABLE
+ )
+ )
+ whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then {
+ (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
+ }
+ whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
+ it.getArgument(0)
+ }
+ actionsProvider = createActionsProvider()
+
+ viewModel.actions.value[2].onClicked?.invoke()
+ verify(uiEventLogger, never())
+ .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any())
+ verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any())
+ actionsProvider.setCompletedScreenshot(validResult)
+ verify(smartActionsProvider)
+ .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid"))
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq(""))
+ }
+
+ private fun createActionsProvider(): ScreenshotActionsProvider {
+ return DefaultScreenshotActionsProvider(
+ context,
+ viewModel,
+ actionIntentExecutor,
+ smartActionsProvider,
+ uiEventLogger,
+ testScope,
+ request,
+ "testid",
+ { transition },
+ requestDismissal,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index e54b53225320..de6108632153 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -42,6 +41,7 @@ import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -50,6 +50,7 @@ import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionI
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
@@ -309,17 +310,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
underTest.addCallback(listener)
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
+ assertThat(deviceUnlockStatus!!.isUnlocked).isFalse()
+
kosmos.sceneInteractor.changeScene(
toScene = Scenes.Lockscreen,
loggingReason = "reason"
)
runCurrent()
- assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
// Call start to begin hydrating based on the scene framework:
@@ -371,14 +375,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
underTest.addCallback(listener)
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
runCurrent()
+
+ assertThat(deviceUnlockStatus!!.isUnlocked).isTrue()
+
kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
runCurrent()
- assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue()
assertThat(currentScene).isEqualTo(Scenes.Gone)
// Call start to begin hydrating based on the scene framework:
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 581ca3b14822..4ace163164f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -28,8 +28,10 @@ import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
+import android.os.Process
import android.os.UserHandle
import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
@@ -41,7 +43,8 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.internal.util.FrameworkStatsLog
-import com.android.server.notification.Flags
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.RankingBuilder
@@ -77,7 +80,7 @@ import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
-@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+@EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -384,13 +387,33 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() {
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertTrue(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun shouldProtectNotification_projectionActive_isFromCoreApp_false() {
+ mediaProjectionCallback.onStart(mediaProjectionInfo)
+
+ val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME)
+
+ assertFalse(controller.shouldProtectNotification(notificationEntry))
+ }
+
+ @Test
fun shouldProtectNotification_projectionActive_sysuiExempt_false() {
// SystemUi context package name is exempt, but in test scenarios its
// com.android.systemui.tests so use that instead of hardcoding
setShareFullScreenViaSystemUi()
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -407,7 +430,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
.thenReturn(PackageManager.PERMISSION_GRANTED)
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertTrue(controller.shouldProtectNotification(notificationEntry))
}
@@ -424,7 +447,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
.thenReturn(PackageManager.PERMISSION_GRANTED)
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -434,7 +457,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
setShareFullScreenViaBugReportHandler()
mediaProjectionCallback.onStart(mediaProjectionInfo)
- val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false)
+ val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME)
assertFalse(controller.shouldProtectNotification(notificationEntry))
}
@@ -657,6 +680,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
private fun setupNotificationEntry(
packageName: String,
isFgs: Boolean = false,
+ isCoreApp: Boolean = false,
overrideVisibility: Boolean = false,
overrideChannelVisibility: Boolean = false,
): NotificationEntry {
@@ -668,8 +692,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
// Developer has marked notification as public
notification.visibility = VISIBILITY_PUBLIC
}
- val notificationEntry =
- NotificationEntryBuilder().setNotification(notification).setPkg(packageName).build()
+ val notificationEntryBuilder =
+ NotificationEntryBuilder().setNotification(notification).setPkg(packageName)
+ if (isCoreApp) {
+ notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10)
+ } else {
+ notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10)
+ }
+ val notificationEntry = notificationEntryBuilder.build()
val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH)
if (overrideChannelVisibility) {
// User doesn't allow private notifications at the channel level
@@ -688,6 +718,10 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
return setupNotificationEntry(packageName, isFgs = true)
}
+ private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry {
+ return setupNotificationEntry(packageName, isCoreApp = true)
+ }
+
private fun setupPublicNotificationEntry(packageName: String): NotificationEntry {
return setupNotificationEntry(packageName, overrideVisibility = true)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 77caeaa6da4d..045bd5d286df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -21,7 +21,6 @@ import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [DeviceEntryRepository] */
@SysUISingleton
@@ -31,21 +30,10 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
private val _isBypassEnabled = MutableStateFlow(false)
override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
- private val _isUnlocked = MutableStateFlow(false)
- override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
-
override suspend fun isLockscreenEnabled(): Boolean {
return isLockscreenEnabled
}
- override fun reportSuccessfulAuthentication() {
- _isUnlocked.value = true
- }
-
- fun setUnlocked(isUnlocked: Boolean) {
- _isUnlocked.value = isUnlocked
- }
-
fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
this.isLockscreenEnabled = isLockscreenEnabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index e73e2950bbb9..bff10a191d5a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -23,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -35,11 +34,10 @@ val Kosmos.deviceEntryInteractor by
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
- trustInteractor = trustInteractor,
- flags = sceneContainerFlags,
- deviceUnlockedInteractor = deviceUnlockedInteractor,
fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ trustInteractor = trustInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index df1cdc2f72cb..14210bc8d15c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -18,14 +18,20 @@ package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.deviceUnlockedInteractor by Fixture {
DeviceUnlockedInteractor(
applicationScope = applicationCoroutineScope,
authenticationInteractor = authenticationInteractor,
deviceEntryRepository = deviceEntryRepository,
+ trustInteractor = trustInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ powerInteractor = powerInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index f65c74fcebc8..5934e0493efd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -22,6 +22,7 @@ import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -35,6 +36,7 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
FLAG_COMPOSE_LOCKSCREEN,
FLAG_MEDIA_IN_SCENE_CONTAINER,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ FLAG_PREDICTIVE_BACK_SYSUI,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 394c8733487a..695e59484014 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -24,9 +24,10 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.StatusBarStateControllerImpl
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.mockito.mock
-var Kosmos.statusBarStateController by
+var Kosmos.statusBarStateController: SysuiStatusBarStateController by
Kosmos.Fixture {
StatusBarStateControllerImpl(
uiEventLogger,
diff --git a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt
index 8a6376326660..b7e31db2e42f 100644
--- a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,10 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.systemui.qs.tiles.impl.sensorprivacy
-/** Constants that vary by compilation configuration. */
-public class Compile {
- /** Whether SystemUI was compiled in debug mode, and supports debug features */
- public static final boolean IS_DEBUG = false;
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsCameraSensorPrivacyToggleTileConfig by
+ Kosmos.Fixture { PolicyModule.provideCameraToggleTileConfig(qsEventLogger) }
+
+val Kosmos.qsMicrophoneSensorPrivacyToggleTileConfig by
+ Kosmos.Fixture { PolicyModule.provideMicrophoneToggleTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 4a2eaf0f7bf6..d08855f190ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -21,6 +21,7 @@ package com.android.systemui.shade
import com.android.systemui.assist.AssistManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
@@ -58,6 +59,7 @@ val Kosmos.shadeControllerSceneImpl by
statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
notificationShadeWindowController = mock<NotificationShadeWindowController>(),
assistManagerLazy = { mock<AssistManager>() },
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
)
}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java
new file mode 100644
index 000000000000..c11c1bba25a7
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.platform.test.ravenwood.bivalenttest;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.Size;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+// Tests for calling simple Android APIs.
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodAndroidApiTest {
+ @Test
+ public void testArrayMapSimple() {
+ final Map<String, String> map = new ArrayMap<>();
+
+ map.put("key1", "value1");
+ assertEquals("value1", map.get("key1"));
+ }
+
+ @Test
+ public void testSizeSimple() {
+ final var size = new Size(1, 2);
+
+ assertEquals(2, size.getHeight());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
new file mode 100644
index 000000000000..6f2465c406d3
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.platform.test.ravenwood.bivalenttest;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood
+public class RavenwoodClassRuleDeviceOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testDeviceOnly() {
+ Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
new file mode 100644
index 000000000000..21b31d1ca3e8
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.bivalenttest;
+
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+// TODO: atest RavenwoodBivalentTest_device fails with the following message.
+// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6`
+// @android.platform.test.annotations.DisabledOnNonRavenwood
+// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well.
+@Ignore
+public class RavenwoodClassRuleRavenwoodOnlyTest {
+ @ClassRule
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ @Test
+ public void testRavenwoodOnly() {
+ Assert.assertTrue(RavenwoodRule.isOnRavenwood());
+ }
+}
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
index 2cd585ff6c9c..4ee9a9c94826 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -40,7 +40,7 @@ public class RavenwoodTestRunnerValidationTest {
public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
public RavenwoodTestRunnerValidationTest() {
- Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled());
+ Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
// Because RavenwoodRule will throw this error before executing the test method,
// we can't do it in the test method itself.
// So instead, we initialize it here.
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
index 8ca34bafc2c6..9d47f3a6bc5d 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -31,13 +31,17 @@ import java.lang.annotation.Target;
* which means if a test class has this annotation, you can't negate it in subclasses or
* on a per-method basis.
*
+ * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT.
+ * See {@link com.android.platform.test.ravenwood.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
+ * for the reason.
+ *
* The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
* propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
*
* @hide
*/
@Inherited
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DisabledOnNonRavenwood {
/**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 9a4d4886d6f0..f4b7ec360dbf 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -25,6 +25,7 @@ import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInP
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -41,27 +42,16 @@ import org.junit.runners.model.Statement;
public class RavenwoodClassRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
- // No special treatment when running outside Ravenwood; run tests as-is
if (!IS_ON_RAVENWOOD) {
- Assume.assumeTrue(shouldEnableOnDevice(description));
- return base;
- }
-
- if (ENABLE_PROBE_IGNORED) {
+ // This should be "Assume", not Assert, but if we use assume here, the device side
+ // test runner would complain.
+ // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest.
+ Assert.assertTrue(shouldEnableOnDevice(description));
+ } else if (ENABLE_PROBE_IGNORED) {
Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- return base;
} else {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue(shouldEnableOnRavenwood(description));
- // Pass through to possible underlying RavenwoodRule for both environment
- // configuration and handling method-level annotations
- base.evaluate();
- }
- };
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
}
+ return base;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 52ea3402fa62..a2e8ec17bbf7 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -28,7 +28,6 @@ import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -278,6 +277,9 @@ public class RavenwoodRule implements TestRule {
return false;
}
}
+ if (description.getTestClass().getAnnotation(DisabledOnNonRavenwood.class) != null) {
+ return false;
+ }
return true;
}
@@ -413,10 +415,9 @@ public class RavenwoodRule implements TestRule {
};
}
- /**
- * Do not use it outside ravenwood core classes.
- */
- public boolean _ravenwood_private$isOptionalValidationEnabled() {
- return ENABLE_OPTIONAL_VALIDATION;
+ public static class _$RavenwoodPrivate {
+ public static boolean isOptionalValidationEnabled() {
+ return ENABLE_OPTIONAL_VALIDATION;
+ }
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 29d1acf5f350..881d6e12ddc7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -286,6 +286,11 @@ filegroup {
// API stub
// =============================================================
+soong_config_module_type_import {
+ from: "frameworks/base/api/Android.bp",
+ module_types: ["non_updatable_exportable_droidstubs"],
+}
+
stubs_defaults {
name: "services-stubs-default",
installable: false,
@@ -301,10 +306,12 @@ stubs_defaults {
filter_packages: ["com.android."],
}
-droidstubs {
+non_updatable_exportable_droidstubs {
name: "services-non-updatable-stubs",
srcs: [":services-non-updatable-sources"],
- defaults: ["services-stubs-default"],
+ defaults: [
+ "services-stubs-default",
+ ],
check_api: {
current: {
api_file: "api/current.txt",
@@ -321,14 +328,34 @@ droidstubs {
targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable.txt",
- tag: ".api.txt",
},
{
targets: ["sdk"],
dir: "apistubs/android/system-server/api",
dest: "android-non-updatable-removed.txt",
- tag: ".removed-api.txt",
},
],
+ soong_config_variables: {
+ release_hidden_api_exportable_stubs: {
+ dists: [
+ {
+ tag: ".exportable.api.txt",
+ },
+ {
+ tag: ".exportable.removed-api.txt",
+ },
+ ],
+ conditions_default: {
+ dists: [
+ {
+ tag: ".api.txt",
+ },
+ {
+ tag: ".removed-api.txt",
+ },
+ ],
+ },
+ },
+ },
api_surface: "system-server",
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 8ac1eb9c90b7..4dae6d5f31e7 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -103,10 +103,10 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
-import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
+import com.android.server.companion.devicepresence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index daa8fdbcab75..9cfb5351f6cf 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -36,8 +36,8 @@ import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
import java.io.PrintWriter;
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index acf683d387a3..8c1116b7a612 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -37,8 +37,8 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
index 407b9daed11c..6cdc02ec67a2 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
@@ -15,27 +15,15 @@
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
-import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.nameForState;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
import static java.util.Objects.requireNonNull;
import android.annotation.MainThread;
@@ -56,21 +44,19 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import android.util.Slog;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.association.AssociationStore.ChangeType;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@SuppressLint("LongLogTag")
-class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
- private static final String TAG = "CDM_BleCompanionDeviceScanner";
+class BleDeviceProcessor implements AssociationStore.OnChangeListener {
+ private static final String TAG = "CDM_BleDeviceProcessor";
interface Callback {
void onBleCompanionDeviceFound(int associationId, int userId);
@@ -78,26 +64,27 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
void onBleCompanionDeviceLost(int associationId, int userId);
}
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull Callback mCallback;
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final Callback mCallback;
// Non-null after init().
- private @Nullable BluetoothAdapter mBtAdapter;
+ @Nullable
+ private BluetoothAdapter mBtAdapter;
// Non-null after init() and when BLE is available. Otherwise - null.
- private @Nullable BluetoothLeScanner mBleScanner;
+ @Nullable
+ private BluetoothLeScanner mBleScanner;
// Only accessed from the Main thread.
private boolean mScanning = false;
- BleCompanionDeviceScanner(
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ BleDeviceProcessor(@NonNull AssociationStore associationStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mCallback = callback;
}
@MainThread
void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) {
- if (DEBUG) Log.i(TAG, "init()");
-
if (mBtAdapter != null) {
throw new IllegalStateException(getClass().getSimpleName() + " is already initialized");
}
@@ -113,9 +100,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
final void restartScan() {
enforceInitialized();
- if (DEBUG) Log.i(TAG , "restartScan()");
if (mBleScanner == null) {
- if (DEBUG) Log.d(TAG, " > BLE is not available");
return;
}
@@ -138,12 +123,8 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
enforceInitialized();
final boolean bleAvailable = mBtAdapter.isLeEnabled();
- if (DEBUG) {
- Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
- }
if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) {
// Nothing changed.
- if (DEBUG) Log.i(TAG, " > BLE status did not change");
return;
}
@@ -153,12 +134,9 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
// Oops, that's a race condition. Can return.
return;
}
- if (DEBUG) Log.i(TAG, " > BLE is now available");
startScan();
} else {
- if (DEBUG) Log.i(TAG, " > BLE is now unavailable");
-
stopScanIfNeeded();
mBleScanner = null;
}
@@ -194,13 +172,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
}
}
if (macAddresses.isEmpty()) {
- if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for.");
return;
- } else {
- if (DEBUG) {
- Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")"
- + "[" + String.join(", ", macAddresses) + "]");
- }
}
final List<ScanFilter> filters = new ArrayList<>(macAddresses.size());
@@ -230,7 +202,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
Slog.i(TAG, "stopBleScan()");
if (!mScanning) {
- if (DEBUG) Log.d(TAG, " > not scanning.");
return;
}
// mScanCallback is non-null here - it cannot be null when mScanning is true.
@@ -252,26 +223,16 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
@MainThread
private void notifyDeviceFound(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
-
- for (AssociationInfo association : associations) {
+ for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+ device.getAddress())) {
mCallback.onBleCompanionDeviceFound(association.getId(), association.getUserId());
}
}
@MainThread
private void notifyDeviceLost(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
-
- for (AssociationInfo association : associations) {
+ for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+ device.getAddress())) {
mCallback.onBleCompanionDeviceLost(association.getId(), association.getUserId());
}
}
@@ -280,17 +241,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1);
- final int state = intent.getIntExtra(EXTRA_STATE, -1);
-
- if (DEBUG) {
- // The action is either STATE_CHANGED or BLE_STATE_CHANGED.
- final String action =
- intent.getAction().replace("android.bluetooth.adapter.", "bt.");
- Log.d(TAG, "on(Broadcast)Receive() " + action + ": "
- + nameForBtState(prevState) + "->" + nameForBtState(state));
- }
-
checkBleState();
}
};
@@ -313,16 +263,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
public void onScanResult(int callbackType, ScanResult result) {
final BluetoothDevice device = result.getDevice();
- if (DEBUG) {
- Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType)
- + " device=" + btDeviceToString(device));
- Log.v(TAG, " > scanResult=" + result);
-
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray()));
- }
-
switch (callbackType) {
case CALLBACK_TYPE_FIRST_MATCH:
notifyDeviceFound(device);
@@ -342,60 +282,20 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
@MainThread
@Override
public void onScanFailed(int errorCode) {
- if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode));
mScanning = false;
}
};
- private static String nameForBtState(int state) {
- return nameForState(state) + "(" + state + ")";
- }
-
private static String nameForBleScanCallbackType(int callbackType) {
- final String name;
- switch (callbackType) {
- case CALLBACK_TYPE_ALL_MATCHES:
- name = "ALL_MATCHES";
- break;
- case CALLBACK_TYPE_FIRST_MATCH:
- name = "FIRST_MATCH";
- break;
- case CALLBACK_TYPE_MATCH_LOST:
- name = "MATCH_LOST";
- break;
- default:
- name = "Unknown";
- }
+ final String name = switch (callbackType) {
+ case CALLBACK_TYPE_ALL_MATCHES -> "ALL_MATCHES";
+ case CALLBACK_TYPE_FIRST_MATCH -> "FIRST_MATCH";
+ case CALLBACK_TYPE_MATCH_LOST -> "MATCH_LOST";
+ default -> "Unknown";
+ };
return name + "(" + callbackType + ")";
}
- private static String nameForBleScanErrorCode(int errorCode) {
- final String name;
- switch (errorCode) {
- case SCAN_FAILED_ALREADY_STARTED:
- name = "ALREADY_STARTED";
- break;
- case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
- name = "APPLICATION_REGISTRATION_FAILED";
- break;
- case SCAN_FAILED_INTERNAL_ERROR:
- name = "INTERNAL_ERROR";
- break;
- case SCAN_FAILED_FEATURE_UNSUPPORTED:
- name = "FEATURE_UNSUPPORTED";
- break;
- case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
- name = "OUT_OF_HARDWARE_RESOURCES";
- break;
- case SCAN_FAILED_SCANNING_TOO_FREQUENTLY:
- name = "SCANNING_TOO_FREQUENTLY";
- break;
- default:
- name = "Unknown";
- }
- return name + "(" + errorCode + ")";
- }
-
private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder()
.setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
.setScanMode(SCAN_MODE_LOW_POWER)
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
index e1a8db41f433..612c156d867e 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
@@ -32,8 +29,6 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.server.companion.association.AssociationStore;
@@ -45,10 +40,10 @@ import java.util.List;
import java.util.Map;
@SuppressLint("LongLogTag")
-public class BluetoothCompanionDeviceConnectionListener
+public class BluetoothDeviceProcessor
extends BluetoothAdapter.BluetoothConnectionCallback
implements AssociationStore.OnChangeListener {
- private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener";
+ private static final String TAG = "CDM_BluetoothDeviceProcessor";
interface Callback {
void onBluetoothCompanionDeviceConnected(int associationId, int userId);
@@ -58,24 +53,25 @@ public class BluetoothCompanionDeviceConnectionListener
void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull Callback mCallback;
- /** A set of ALL connected BT device (not only companion.) */
- private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final ObservableUuidStore mObservableUuidStore;
+ @NonNull
+ private final Callback mCallback;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
+ /** A set of ALL connected BT device (not only companion.) */
+ @NonNull
+ private final Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
- BluetoothCompanionDeviceConnectionListener(UserManager userManager,
- @NonNull AssociationStore associationStore,
+ BluetoothDeviceProcessor(@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mCallback = callback;
}
- public void init(@NonNull BluetoothAdapter btAdapter) {
- if (DEBUG) Log.i(TAG, "init()");
-
+ void init(@NonNull BluetoothAdapter btAdapter) {
btAdapter.registerBluetoothConnectionCallback(
new HandlerExecutor(Handler.getMain()), /* callback */this);
mAssociationStore.registerLocalListener(this);
@@ -87,13 +83,9 @@ public class BluetoothCompanionDeviceConnectionListener
*/
@Override
public void onDeviceConnected(@NonNull BluetoothDevice device) {
- if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
-
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
- final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.put(macAddress, device) != null) {
- if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
return;
}
@@ -108,18 +100,9 @@ public class BluetoothCompanionDeviceConnectionListener
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device,
int reason) {
- if (DEBUG) {
- Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
- Log.d(TAG, " reason=" + disconnectReasonToString(reason));
- }
-
final MacAddress macAddress = MacAddress.fromString(device.getAddress());
- final int userId = UserHandle.myUserId();
if (mAllConnectedDevices.remove(macAddress) == null) {
- if (DEBUG) {
- Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
- }
return;
}
@@ -130,22 +113,6 @@ public class BluetoothCompanionDeviceConnectionListener
int userId = UserHandle.myUserId();
final List<AssociationInfo> associations =
mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForUser(userId);
- final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
-
- final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
- ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
-
- if (DEBUG) {
- Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
- + " connected=" + connected);
- if (associations.isEmpty()) {
- Log.d(TAG, " > No CDM associations");
- } else {
- Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray()));
- }
- }
for (AssociationInfo association : associations) {
if (!association.isNotifyOnDeviceNearby()) continue;
@@ -157,44 +124,25 @@ public class BluetoothCompanionDeviceConnectionListener
}
}
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+ final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
+
for (ObservableUuid uuid : observableUuids) {
if (deviceUuids.contains(uuid.getUuid())) {
mCallback.onDevicePresenceEventByUuid(uuid, connected ? EVENT_BT_CONNECTED
- : EVENT_BT_DISCONNECTED);
+ : EVENT_BT_DISCONNECTED);
}
}
}
@Override
public void onAssociationAdded(AssociationInfo association) {
- if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
-
if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
mCallback.onBluetoothCompanionDeviceConnected(
association.getId(), association.getUserId());
}
}
-
- @Override
- public void onAssociationRemoved(AssociationInfo association) {
- // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping
- // required.
- }
-
- @Override
- public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
- if (DEBUG) {
- Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged
- + " " + association);
- }
-
- if (!addressChanged) {
- // Don't need to do anything.
- return;
- }
-
- // At the moment CDM does allow changing association addresses, so we will never come here.
- // This will be implemented when CDM support updating addresses.
- throw new IllegalArgumentException("Address changes are not supported.");
- }
}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index b6348ea9594d..60f46887fa5c 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
index c01c3195e04d..5923e70c2300 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index 092886cbfe18..cfb7f337242b 100644
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
@@ -24,6 +24,7 @@ import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
+import static android.content.Context.BLUETOOTH_SERVICE;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -35,6 +36,7 @@ import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
import android.companion.DevicePresenceEvent;
@@ -49,7 +51,6 @@ import android.os.ParcelUuid;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserManager;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -80,8 +81,7 @@ import java.util.Set;
*/
@SuppressLint("LongLogTag")
public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
+ BluetoothDeviceProcessor.Callback, BleDeviceProcessor.Callback {
private static final String TAG = "CDM_DevicePresenceProcessor";
@NonNull
@@ -93,9 +93,9 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull
private final ObservableUuidStore mObservableUuidStore;
@NonNull
- private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ private final BluetoothDeviceProcessor mBluetoothDeviceProcessor;
@NonNull
- private final BleCompanionDeviceScanner mBleScanner;
+ private final BleDeviceProcessor mBleDeviceProcessor;
@NonNull
private final PowerManagerInternal mPowerManagerInternal;
@NonNull
@@ -142,7 +142,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
public DevicePresenceProcessor(@NonNull Context context,
@NonNull CompanionAppBinder companionAppBinder,
- UserManager userManager,
+ @NonNull UserManager userManager,
@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore,
@NonNull PowerManagerInternal powerManagerInternal) {
@@ -151,26 +151,28 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mUserManager = userManager;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
+ mBluetoothDeviceProcessor = new BluetoothDeviceProcessor(associationStore,
+ mObservableUuidStore, this);
+ mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
mPowerManagerInternal = powerManagerInternal;
}
/** Initialize {@link DevicePresenceProcessor} */
public void init(Context context) {
- if (DEBUG) Slog.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
+ BluetoothManager bm = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE);
+ if (bm == null) {
+ Slog.w(TAG, "BluetoothManager is not available.");
+ return;
+ }
+ final BluetoothAdapter btAdapter = bm.getAdapter();
+ if (btAdapter == null) {
Slog.w(TAG, "BluetoothAdapter is NOT available.");
+ return;
}
+ mBluetoothDeviceProcessor.init(btAdapter);
+ mBleDeviceProcessor.init(context, btAdapter);
+
mAssociationStore.registerLocalListener(this);
}
@@ -280,7 +282,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* For legacy device presence below Android V.
*
* @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
+ * int)}
*/
@Deprecated
public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -310,7 +312,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* For legacy device presence below Android V.
*
* @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
+ * int)}
*/
@Deprecated
public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -496,7 +498,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
// Stop the BLE scan if all devices report BT connected status and BLE was present.
if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
+ mBleDeviceProcessor.stopScanIfNeeded();
}
}
@@ -513,7 +515,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
}
// Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
+ mBleDeviceProcessor.startScan();
onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
// If current device is BLE present but BT is disconnected , means it will be
@@ -724,7 +726,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
final ParcelUuid parcelUuid = uuid.getUuid();
final int userId = uuid.getUserId();
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
- onDeviceLocked(/* associationId */ -1, userId, eventType, parcelUuid);
+ onDeviceLocked(NO_ASSOCIATION, userId, eventType, parcelUuid);
return;
}
@@ -930,10 +932,6 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@Override
public void onAssociationRemoved(@NonNull AssociationInfo association) {
final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
mConnectedBtDevices.remove(id);
mNearbyBleDevices.remove(id);
@@ -1004,8 +1002,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (deviceEvents != null) {
deviceEvents.removeIf(deviceEvent ->
deviceEvent.getEvent() == EVENT_BLE_APPEARED
- && Objects.equals(deviceEvent.getUuid(), uuid)
- && deviceEvent.getAssociationId() == associationId);
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
}
}
}
@@ -1018,8 +1016,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (deviceEvents != null) {
deviceEvents.removeIf(deviceEvent ->
deviceEvent.getEvent() == EVENT_BT_CONNECTED
- && Objects.equals(deviceEvent.getUuid(), uuid)
- && deviceEvent.getAssociationId() == associationId);
+ && Objects.equals(deviceEvent.getUuid(), uuid)
+ && deviceEvent.getAssociationId() == associationId);
}
}
}
@@ -1054,7 +1052,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
return;
}
- switch(event) {
+ switch (event) {
case EVENT_BLE_APPEARED:
onBleCompanionDeviceFound(
associationInfo.getId(), associationInfo.getUserId());
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
index 9cfa2705cb2f..c9f60ca639d4 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
index fa0f6bd92acb..4678a165b83f 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index da90bdbb9041..93243fca9dd2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1108,10 +1108,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mParams.dump(fout, indent + indent);
fout.println(indent + "mVirtualDisplayIds: ");
synchronized (mVirtualDeviceLock) {
- fout.println(" mDevicePolicies: " + mDevicePolicies);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
fout.println(indent + " " + mVirtualDisplays.keyAt(i));
}
+ fout.println(" mDevicePolicies: " + mDevicePolicies);
fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7f5867fb1a74..c7d99424f72b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -246,7 +246,7 @@ java_library_static {
"biometrics_flags_lib",
"am_flags_lib",
"com_android_server_accessibility_flags_lib",
- "com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0a2aaeba57b4..7ea82b095533 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5555,6 +5555,7 @@ public final class ActiveServices {
boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
throws TransactionTooLargeException {
if (r.app != null && r.app.isThreadReady()) {
+ r.updateOomAdjSeq();
sendServiceArgsLocked(r, execInFg, false);
return null;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 55b161ad6348..dcda5c228ceb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -55,9 +55,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_BACKGROUND_CHECK = DEBUG_ALL || false;
static final boolean DEBUG_BACKUP = DEBUG_ALL || false;
static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
- static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
- static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
static final boolean DEBUG_FREEZER = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a66c23f7cf63..b20135c958cd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12084,18 +12084,23 @@ public class ActivityManagerService extends IActivityManager.Stub
for (int i=0; i<items.size(); i++) {
MemItem mi = items.get(i);
if (!isCompact) {
- pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
+ String printFormat = "%s%s: %s%s\n";
+ if ((dumpPss && dumpSwapPss) || dumpPrivateDirty) {
+ StringBuilder format = new StringBuilder();
+ format.append("%s%s: %-60s%s");
+ if (dumpSwapPss) {
+ format.append(String.format("(%s in swap%s", stringifyKBSize(mi.swapPss),
+ dumpPrivateDirty ? ", " : ")"));
+ }
+ if (dumpPrivateDirty) {
+ format.append(String.format("%s%s private dirty)", dumpSwapPss ? "" : "(",
+ stringifyKBSize(mi.mPrivateDirty)));
+ }
+ printFormat = format.append("\n").toString();
+ }
+ pw.printf(printFormat, prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss),
mi.label,
mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : "");
- if (dumpPss && dumpSwapPss) {
- pw.printf("(%s in swap%s", stringifyKBSize(mi.swapPss),
- dumpPrivateDirty ? ", " : ")");
- }
- if (dumpPrivateDirty) {
- pw.printf("%s%s private dirty)", dumpSwapPss ? "" : "(",
- stringifyKBSize(mi.mPrivateDirty));
- }
- pw.printf("\n");
} else if (mi.isProc) {
pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
pw.print(","); pw.print(mi.id); pw.print(",");
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 48a9d6af0df4..6779f7a37f20 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -351,6 +351,7 @@ public final class ProcessList {
// LMK_UPDATE_PROPS
// LMK_KILL_OCCURRED
// LMK_START_MONITORING
+ // LMK_BOOT_COMPLETED
static final byte LMK_TARGET = 0;
static final byte LMK_PROCPRIO = 1;
static final byte LMK_PROCREMOVE = 2;
@@ -361,6 +362,7 @@ public final class ProcessList {
static final byte LMK_UPDATE_PROPS = 7;
static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event
static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier
+ static final byte LMK_BOOT_COMPLETED = 10;
// Low Memory Killer Daemon command codes.
// These must be kept in sync with async_event_type definitions in lmkd.h
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 951f676dd098..656611a79595 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1820,18 +1820,17 @@ public class AudioDeviceBroker {
+ "received with null profile proxy: "
+ btInfo)).printLog(TAG));
} else {
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ final Pair<Integer, Boolean> codecAndChanged =
mBtHelper.getCodecWithFallback(btInfo.mDevice,
btInfo.mProfile, btInfo.mIsLeOutput,
"MSG_L_SET_BT_ACTIVE_DEVICE");
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
- (btInfo.mProfile
- != BluetoothProfile.LE_AUDIO
+ mDeviceInventory.onSetBtActiveDevice(btInfo, codecAndChanged.first,
+ (btInfo.mProfile != BluetoothProfile.LE_AUDIO
|| btInfo.mIsLeOutput)
- ? mAudioService.getBluetoothContextualVolumeStream()
- : AudioSystem.STREAM_DEFAULT);
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile
== BluetoothProfile.HEARING_AID) {
@@ -1866,13 +1865,13 @@ public class AudioDeviceBroker {
break;
case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
+ final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(
+ btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
synchronized (mDeviceStateLock) {
- mDeviceInventory.onBluetoothDeviceConfigChange(
- btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+ mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+ codecAndChanged.first, codecAndChanged.second,
+ BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
} break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 14428c41ef26..ebe9c636d6f6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -868,7 +868,8 @@ public class AudioDeviceInventory {
@GuardedBy("mDeviceBroker.mDeviceStateLock")
/*package*/ void onBluetoothDeviceConfigChange(
@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
+ boolean codecChanged, int event) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
@@ -916,14 +917,12 @@ public class AudioDeviceInventory {
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
- boolean codecChange = false;
if (btInfo.mProfile == BluetoothProfile.A2DP
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) {
- if (di.mDeviceCodecFormat != codec) {
+ if (codecChanged) {
di.mDeviceCodecFormat = codec;
mConnectedDevices.replace(key, di);
- codecChange = true;
final int res = mAudioSystem.handleDeviceConfigChange(
btInfo.mAudioSystemDevice, address,
BtHelper.getName(btDevice), codec);
@@ -947,7 +946,7 @@ public class AudioDeviceInventory {
}
}
}
- if (!codecChange) {
+ if (!codecChanged) {
updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 85acf707677a..570d4e9857e6 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -53,6 +53,12 @@ class AudioManagerShellCommand extends ShellCommand {
return getSoundDoseValue();
case "reset-sound-dose-timeout":
return resetSoundDoseTimeout();
+ case "set-volume":
+ return setVolume();
+ case "adj-mute":
+ return adjMute();
+ case "adj-unmute":
+ return adjUnmute();
}
return 0;
}
@@ -78,6 +84,12 @@ class AudioManagerShellCommand extends ShellCommand {
pw.println(" Returns the current sound dose value");
pw.println(" reset-sound-dose-timeout");
pw.println(" Resets the sound dose timeout used for momentary exposure");
+ pw.println(" set-volume STREAM_TYPE VOLUME_INDEX");
+ pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX");
+ pw.println(" adj-mute STREAM_TYPE");
+ pw.println(" mutes the STREAM_TYPE");
+ pw.println(" adj-unmute STREAM_TYPE");
+ pw.println(" unmutes the STREAM_TYPE");
}
private int setSurroundFormatEnabled() {
@@ -216,4 +228,54 @@ class AudioManagerShellCommand extends ShellCommand {
getOutPrintWriter().println("Reset sound dose momentary exposure timeout");
return 0;
}
+
+ private int setVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int index = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.setStreamVolume("
+ + stream + ", " + index + ", 0)");
+ am.setStreamVolume(stream, index, 0);
+ return 0;
+ }
+
+ private int adjMute() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", AudioManager.ADJUST_MUTE, 0)");
+ am.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+ return 0;
+ }
+
+ private int adjUnmute() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", AudioManager.ADJUST_UNMUTE, 0)");
+ am.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+ return 0;
+ }
+
+ private int readIntArg() throws IllegalArgumentException {
+ String argText = getNextArg();
+
+ if (argText == null) {
+ getErrPrintWriter().println("Error: no argument provided");
+ throw new IllegalArgumentException("No argument provided");
+ }
+
+ int argIntVal = Integer.MIN_VALUE;
+ try {
+ argIntVal = Integer.parseInt(argText);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: wrong format for argument " + argText);
+ throw new IllegalArgumentException("Wrong format for argument " + argText);
+ }
+
+ return argIntVal;
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ed58c4033c4c..40099581c68b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -47,6 +47,7 @@ import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
+import static com.android.media.audio.Flags.vgsVssSyncMuteOrder;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -4544,6 +4545,8 @@ public class AudioService extends IAudioService.Stub
+ setStreamVolumeOrder());
pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+ roForegroundAudioControl());
+ pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:"
+ + vgsVssSyncMuteOrder());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -8317,13 +8320,23 @@ public class AudioService extends IAudioService.Stub
synced = true;
continue;
}
+ if (vgsVssSyncMuteOrder()) {
+ if ((isMuted() != streamMuted) && isVssMuteBijective(
+ stream)) {
+ mStreamStates[stream].mute(isMuted(),
+ "VGS.applyAllVolumes#1");
+ }
+ }
if (indexForStream != index) {
mStreamStates[stream].setIndex(index * 10, device, caller,
true /*hasModifyAudioSettings*/);
}
- if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
- mStreamStates[stream].mute(isMuted(),
- "VGS.applyAllVolumes#1");
+ if (!vgsVssSyncMuteOrder()) {
+ if ((isMuted() != streamMuted) && isVssMuteBijective(
+ stream)) {
+ mStreamStates[stream].mute(isMuted(),
+ "VGS.applyAllVolumes#1");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index f3a5fdb3cacc..edeabdc5243c 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -98,9 +98,16 @@ public class BtHelper {
private @Nullable BluetoothLeAudio mLeAudio;
+ private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig;
+
// Reference to BluetoothA2dp to query for AbsoluteVolume.
private @Nullable BluetoothA2dp mA2dp;
+ private @Nullable BluetoothCodecConfig mA2dpCodecConfig;
+
+ private @AudioSystem.AudioFormatNativeEnumForBtCodec
+ int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+
// If absolute volume is supported in AVRCP device
private boolean mAvrcpAbsVolSupported = false;
@@ -265,12 +272,15 @@ public class BtHelper {
}
}
- /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec(
+ private synchronized Pair<Integer, Boolean> getCodec(
@NonNull BluetoothDevice device, @AudioService.BtProfile int profile) {
+
switch (profile) {
case BluetoothProfile.A2DP: {
+ boolean changed = mA2dpCodecConfig != null;
if (mA2dp == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothCodecStatus btCodecStatus = null;
try {
@@ -279,17 +289,24 @@ public class BtHelper {
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
if (btCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mA2dpCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
- return AudioSystem.bluetoothA2dpCodecToAudioFormat(btCodecConfig.getCodecType());
+ changed = !btCodecConfig.equals(mA2dpCodecConfig);
+ mA2dpCodecConfig = btCodecConfig;
+ return new Pair<>(AudioSystem.bluetoothA2dpCodecToAudioFormat(
+ btCodecConfig.getCodecType()), changed);
}
case BluetoothProfile.LE_AUDIO: {
+ boolean changed = mLeAudioCodecConfig != null;
if (mLeAudio == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothLeAudioCodecStatus btLeCodecStatus = null;
int groupId = mLeAudio.getGroupId(device);
@@ -299,42 +316,54 @@ public class BtHelper {
Log.e(TAG, "Exception while getting status of " + device, e);
}
if (btLeCodecStatus == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
BluetoothLeAudioCodecConfig btLeCodecConfig =
btLeCodecStatus.getOutputCodecConfig();
if (btLeCodecConfig == null) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ mLeAudioCodecConfig = null;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
}
- return AudioSystem.bluetoothLeCodecToAudioFormat(btLeCodecConfig.getCodecType());
+ changed = !btLeCodecConfig.equals(mLeAudioCodecConfig);
+ mLeAudioCodecConfig = btLeCodecConfig;
+ return new Pair<>(AudioSystem.bluetoothLeCodecToAudioFormat(
+ btLeCodecConfig.getCodecType()), changed);
+ }
+ case BluetoothProfile.LE_AUDIO_BROADCAST: {
+ // We assume LC3 for LE Audio broadcast codec as there is no API to get the codec
+ // config on LE Broadcast profile proxy.
+ boolean changed = mLeAudioBroadcastCodec != AudioSystem.AUDIO_FORMAT_LC3;
+ mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_LC3;
+ return new Pair<>(mLeAudioBroadcastCodec, changed);
}
default:
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
}
}
- /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec
- int getCodecWithFallback(
- @NonNull BluetoothDevice device, @AudioService.BtProfile int profile,
- boolean isLeOutput, @NonNull String source) {
+ /*package*/ synchronized Pair<Integer, Boolean>
+ getCodecWithFallback(@NonNull BluetoothDevice device,
+ @AudioService.BtProfile int profile,
+ boolean isLeOutput, @NonNull String source) {
// For profiles other than A2DP and LE Audio output, the audio codec format must be
// AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format
// only if audio HW module selection based on format is supported for the device type.
if (!(profile == BluetoothProfile.A2DP
|| (isLeOutput && ((profile == BluetoothProfile.LE_AUDIO)
|| (profile == BluetoothProfile.LE_AUDIO_BROADCAST))))) {
- return AudioSystem.AUDIO_FORMAT_DEFAULT;
+ return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false);
}
- @AudioSystem.AudioFormatNativeEnumForBtCodec int codec =
+ Pair<Integer, Boolean> codecAndChanged =
getCodec(device, profile);
- if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) {
+ if (codecAndChanged.first == AudioSystem.AUDIO_FORMAT_DEFAULT) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"getCodec DEFAULT from " + source + " fallback to "
+ (profile == BluetoothProfile.A2DP ? "SBC" : "LC3")));
- return profile == BluetoothProfile.A2DP
- ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3;
+ return new Pair<>(profile == BluetoothProfile.A2DP
+ ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true);
}
- return codec;
+ return codecAndChanged;
}
// @GuardedBy("mDeviceBroker.mSetModeLock")
@@ -539,15 +568,19 @@ public class BtHelper {
break;
case BluetoothProfile.A2DP:
mA2dp = null;
+ mA2dpCodecConfig = null;
break;
case BluetoothProfile.HEARING_AID:
mHearingAid = null;
break;
case BluetoothProfile.LE_AUDIO:
mLeAudio = null;
+ mLeAudioCodecConfig = null;
break;
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.LE_AUDIO_BROADCAST:
+ mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+ break;
+ case BluetoothProfile.A2DP_SINK:
// nothing to do in BtHelper
break;
default:
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 3ede0a2597d9..028b9b0bcbc0 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -26,7 +26,7 @@ import com.android.server.SystemService;
import java.util.ArrayList;
-public class BroadcastRadioService extends SystemService {
+public final class BroadcastRadioService extends SystemService {
private final IRadioService mServiceImpl;
public BroadcastRadioService(Context context) {
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 42b2682cf530..16514fa813dc 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -52,7 +52,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
private static final List<String> SERVICE_NAMES = Arrays.asList(
IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab");
- private final BroadcastRadioServiceImpl mHalAidl;
+ private final BroadcastRadioServiceImpl mAidlHalClient;
private final BroadcastRadioService mService;
/**
@@ -77,14 +77,14 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
@VisibleForTesting
IRadioServiceAidlImpl(BroadcastRadioService service, BroadcastRadioServiceImpl halAidl) {
mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
- mHalAidl = Objects.requireNonNull(halAidl,
+ mAidlHalClient = Objects.requireNonNull(halAidl,
"Broadcast radio service implementation for AIDL HAL cannot be null");
}
@Override
public List<RadioManager.ModuleProperties> listModules() {
mService.enforcePolicyAccess();
- return mHalAidl.listModules();
+ return mAidlHalClient.listModules();
}
@Override
@@ -97,7 +97,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
if (callback == null) {
throw new IllegalArgumentException("Callback must not be null");
}
- return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+ return mAidlHalClient.openSession(moduleId, bandConfig, withAudio, callback);
}
@Override
@@ -110,7 +110,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
Objects.requireNonNull(listener, "Announcement listener cannot be null");
mService.enforcePolicyAccess();
- return mHalAidl.addAnnouncementListener(enabledTypes, listener);
+ return mAidlHalClient.addAnnouncementListener(enabledTypes, listener);
}
@Override
@@ -126,10 +126,10 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub {
radioPrintWriter.printf("BroadcastRadioService\n");
radioPrintWriter.increaseIndent();
- radioPrintWriter.printf("AIDL HAL:\n");
+ radioPrintWriter.printf("AIDL HAL client:\n");
radioPrintWriter.increaseIndent();
- mHalAidl.dumpInfo(radioPrintWriter);
+ mAidlHalClient.dumpInfo(radioPrintWriter);
radioPrintWriter.decreaseIndent();
radioPrintWriter.decreaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index bc72a4be18be..ab083429a200 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -49,8 +49,8 @@ import java.util.OptionalInt;
final class IRadioServiceHidlImpl extends IRadioService.Stub {
private static final String TAG = "BcRadioSrvHidl";
- private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1;
- private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2;
+ private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1Client;
+ private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2Client;
private final Object mLock = new Object();
@@ -61,10 +61,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
IRadioServiceHidlImpl(BroadcastRadioService service) {
mService = Objects.requireNonNull(service, "broadcast radio service cannot be null");
- mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
- mV1Modules = mHal1.loadModules();
+ mHal1Client = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
+ mV1Modules = mHal1Client.loadModules();
OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
- mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
+ mHal2Client = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
max.isPresent() ? max.getAsInt() + 1 : 0);
}
@@ -73,17 +73,17 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
com.android.server.broadcastradio.hal1.BroadcastRadioService hal1,
com.android.server.broadcastradio.hal2.BroadcastRadioService hal2) {
mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null");
- mHal1 = Objects.requireNonNull(hal1,
+ mHal1Client = Objects.requireNonNull(hal1,
"Broadcast radio service implementation for HIDL 1 HAL cannot be null");
- mV1Modules = mHal1.loadModules();
- mHal2 = Objects.requireNonNull(hal2,
+ mV1Modules = mHal1Client.loadModules();
+ mHal2Client = Objects.requireNonNull(hal2,
"Broadcast radio service implementation for HIDL 2 HAL cannot be null");
}
@Override
public List<RadioManager.ModuleProperties> listModules() {
mService.enforcePolicyAccess();
- Collection<RadioManager.ModuleProperties> v2Modules = mHal2.listModules();
+ Collection<RadioManager.ModuleProperties> v2Modules = mHal2Client.listModules();
List<RadioManager.ModuleProperties> modules;
synchronized (mLock) {
modules = new ArrayList<>(mV1Modules.size() + v2Modules.size());
@@ -102,10 +102,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
mService.enforcePolicyAccess();
Objects.requireNonNull(callback, "Callback must not be null");
synchronized (mLock) {
- if (mHal2.hasModule(moduleId)) {
- return mHal2.openSession(moduleId, bandConfig, withAudio, callback);
+ if (mHal2Client.hasModule(moduleId)) {
+ return mHal2Client.openSession(moduleId, bandConfig, withAudio, callback);
} else {
- return mHal1.openTuner(moduleId, bandConfig, withAudio, callback);
+ return mHal1Client.openTuner(moduleId, bandConfig, withAudio, callback);
}
}
}
@@ -121,12 +121,12 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
mService.enforcePolicyAccess();
synchronized (mLock) {
- if (!mHal2.hasAnyModules()) {
+ if (!mHal2Client.hasAnyModules()) {
Slog.w(TAG, "There are no HAL 2.0 modules registered");
return new AnnouncementAggregator(listener, mLock);
}
- return mHal2.addAnnouncementListener(enabledTypes, listener);
+ return mHal2Client.addAnnouncementListener(enabledTypes, listener);
}
}
@@ -143,18 +143,18 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub {
radioPw.printf("BroadcastRadioService\n");
radioPw.increaseIndent();
- radioPw.printf("HAL1: %s\n", mHal1);
+ radioPw.printf("HAL1 client: %s\n", mHal1Client);
radioPw.increaseIndent();
synchronized (mLock) {
- radioPw.printf("Modules of HAL1: %s\n", mV1Modules);
+ radioPw.printf("Modules of HAL1 client: %s\n", mV1Modules);
}
radioPw.decreaseIndent();
- radioPw.printf("HAL2:\n");
+ radioPw.printf("HAL2 client:\n");
radioPw.increaseIndent();
- mHal2.dumpInfo(radioPw);
+ mHal2Client.dumpInfo(radioPw);
radioPw.decreaseIndent();
radioPw.decreaseIndent();
diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
index 4b847a27c4de..c705ebe686f2 100644
--- a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
+++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java
@@ -48,8 +48,8 @@ public final class RadioServiceUserController {
* @return foreground user id.
*/
public static int getCurrentUser() {
- final long identity = Binder.clearCallingIdentity();
int userId = UserHandle.USER_NULL;
+ final long identity = Binder.clearCallingIdentity();
try {
userId = ActivityManager.getCurrentUser();
} catch (RuntimeException e) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
index 219ee4c3229a..08ff6627785a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java
@@ -18,12 +18,13 @@ package com.android.server.broadcastradio.hal1;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
import java.util.Map;
import java.util.Set;
-class Convert {
+final class Convert {
private static final String TAG = "BcRadio1Srv.Convert";
@@ -34,12 +35,12 @@ class Convert {
* side, which requires several separate java calls for each element.
*
* @param map map to convert.
- * @returns array (sized the same as map) of two-element string arrays
- * (first element is the key, second is value).
+ * @return array (sized the same as map) of two-element string arrays
+ * (first element is the key, second is value).
*/
static @NonNull String[][] stringMapToNative(@Nullable Map<String, String> map) {
if (map == null) {
- Slog.v(TAG, "map is null, returning zero-elements array");
+ Slogf.v(TAG, "map is null, returning zero-elements array");
return new String[0][0];
}
@@ -54,7 +55,7 @@ class Convert {
i++;
}
- Slog.v(TAG, "converted " + i + " element(s)");
+ Slogf.v(TAG, "converted " + i + " element(s)");
return arr;
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index 8e5f6b5b8624..7cac4091c583 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -26,7 +26,6 @@ import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
@@ -44,9 +43,9 @@ class Tuner extends ITuner.Stub {
private final long mNativeContext;
private final Object mLock = new Object();
- @NonNull private final TunerCallback mTunerCallback;
- @NonNull private final ITunerCallback mClientCallback;
- @NonNull private final IBinder.DeathRecipient mDeathRecipient;
+ private final TunerCallback mTunerCallback;
+ private final ITunerCallback mClientCallback;
+ private final IBinder.DeathRecipient mDeathRecipient;
private boolean mIsClosed = false;
private boolean mIsMuted = false;
@@ -122,7 +121,7 @@ class Tuner extends ITuner.Stub {
private boolean checkConfiguredLocked() {
if (mTunerCallback.isInitialConfigurationDone()) return true;
- Slog.w(TAG, "Initial configuration is still pending, skipping the operation");
+ Slogf.w(TAG, "Initial configuration is still pending, skipping the operation");
return false;
}
@@ -159,14 +158,14 @@ class Tuner extends ITuner.Stub {
checkNotClosedLocked();
if (mIsMuted == mute) return;
mIsMuted = mute;
- Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
+ Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
}
}
@Override
public boolean isMuted() {
if (!mWithAudio) {
- Slog.w(TAG, "Tuner did not request audio, pretending it was muted");
+ Slogf.w(TAG, "Tuner did not request audio, pretending it was muted");
return true;
}
synchronized (mLock) {
@@ -210,7 +209,7 @@ class Tuner extends ITuner.Stub {
if (selector == null) {
throw new IllegalArgumentException("The argument must not be a null pointer");
}
- Slog.i(TAG, "Tuning to " + selector);
+ Slogf.i(TAG, "Tuning to " + selector);
synchronized (mLock) {
checkNotClosedLocked();
if (!checkConfiguredLocked()) return;
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index aa43b7581fe7..e013643a812d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -25,7 +25,8 @@ import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
import java.util.List;
import java.util.Map;
@@ -42,8 +43,8 @@ class TunerCallback implements ITunerCallback {
*/
private final long mNativeContext;
- @NonNull private final Tuner mTuner;
- @NonNull private final ITunerCallback mClientCallback;
+ private final Tuner mTuner;
+ private final ITunerCallback mClientCallback;
private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>();
private boolean mInitialConfigurationDone = false;
@@ -76,7 +77,7 @@ class TunerCallback implements ITunerCallback {
try {
func.run();
} catch (RemoteException e) {
- Slog.e(TAG, "client died", e);
+ Slogf.e(TAG, "client died", e);
}
}
@@ -107,7 +108,7 @@ class TunerCallback implements ITunerCallback {
@Override
public void onTuneFailed(int result, ProgramSelector selector) {
- Slog.e(TAG, "Not applicable for HAL 1.x");
+ Slogf.e(TAG, "Not applicable for HAL 1.x");
}
@Override
@@ -160,7 +161,7 @@ class TunerCallback implements ITunerCallback {
try {
modified = mTuner.getProgramList(filter.getVendorFilter());
} catch (IllegalStateException ex) {
- Slog.d(TAG, "Program list not ready yet");
+ Slogf.d(TAG, "Program list not ready yet");
return;
}
Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet());
@@ -175,12 +176,12 @@ class TunerCallback implements ITunerCallback {
@Override
public void onConfigFlagUpdated(int flag, boolean value) {
- Slog.w(TAG, "Not applicable for HAL 1.x");
+ Slogf.w(TAG, "Not applicable for HAL 1.x");
}
@Override
public void onParametersUpdated(Map<String, String> parameters) {
- Slog.w(TAG, "Not applicable for HAL 1.x");
+ Slogf.w(TAG, "Not applicable for HAL 1.x");
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
index 85c13aee37c9..0327ee79d39c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -23,20 +23,20 @@ import android.hardware.radio.IAnnouncementListener;
import android.hardware.radio.ICloseHandle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
-public class AnnouncementAggregator extends ICloseHandle.Stub {
+public final class AnnouncementAggregator extends ICloseHandle.Stub {
private static final String TAG = "BcRadio2Srv.AnnAggr";
private final Object mLock;
- @NonNull private final IAnnouncementListener mListener;
+ private final IAnnouncementListener mListener;
private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
@GuardedBy("mLock")
@@ -77,14 +77,16 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
public void binderDied() {
try {
close();
- } catch (RemoteException ex) {}
+ } catch (RemoteException ex) {
+ Slogf.e(TAG, ex, "Cannot close Announcement aggregator for DeathRecipient");
+ }
}
}
private void onListUpdated() {
synchronized (mLock) {
if (mIsClosed) {
- Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+ Slogf.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
return;
}
List<Announcement> combined = new ArrayList<>();
@@ -94,7 +96,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
try {
mListener.onListUpdated(combined);
} catch (RemoteException ex) {
- Slog.e(TAG, "mListener.onListUpdated() failed: ", ex);
+ Slogf.e(TAG, "mListener.onListUpdated() failed: ", ex);
}
}
}
@@ -111,7 +113,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
try {
closeHandle = module.addAnnouncementListener(enabledTypes, watcher);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to add announcement listener", ex);
+ Slogf.e(TAG, "Failed to add announcement listener", ex);
return;
}
watcher.setCloseHandle(closeHandle);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 1e31f200fd47..3198842c1ff3 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -29,8 +29,8 @@ import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IHwBinder.DeathRecipient;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,7 +38,6 @@ import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -55,16 +54,17 @@ public final class BroadcastRadioService {
private int mNextModuleId;
@GuardedBy("mLock")
- private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>();
+ private final Map<String, Integer> mServiceNameToModuleIdMap = new ArrayMap<>();
// Map from module ID to RadioModule created by mServiceListener.onRegistration().
@GuardedBy("mLock")
- private final Map<Integer, RadioModule> mModules = new HashMap<>();
+ private final Map<Integer, RadioModule> mModules = new ArrayMap<>();
- private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
+ private final IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
@Override
public void onRegistration(String fqName, String serviceName, boolean preexisting) {
- Slog.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + ")");
+ Slogf.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting
+ + ")");
Integer moduleId;
synchronized (mLock) {
// If the service has been registered before, reuse its previous module ID.
@@ -75,13 +75,13 @@ public final class BroadcastRadioService {
moduleId = mNextModuleId;
}
- RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName);
- if (module == null) {
+ RadioModule radioModule = RadioModule.tryLoadingModule(moduleId, serviceName);
+ if (radioModule == null) {
return;
}
- Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
+ Slogf.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
+ " (HAL 2.0)");
- RadioModule prevModule = mModules.put(moduleId, module);
+ RadioModule prevModule = mModules.put(moduleId, radioModule);
if (prevModule != null) {
prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
}
@@ -92,7 +92,7 @@ public final class BroadcastRadioService {
}
try {
- module.getService().linkToDeath(mDeathRecipient, moduleId);
+ radioModule.getService().linkToDeath(mDeathRecipient, moduleId);
} catch (RemoteException ex) {
// Service has already died, so remove its entry from mModules.
mModules.remove(moduleId);
@@ -101,10 +101,10 @@ public final class BroadcastRadioService {
}
};
- private DeathRecipient mDeathRecipient = new DeathRecipient() {
+ private final DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void serviceDied(long cookie) {
- Slog.v(TAG, "serviceDied(" + cookie + ")");
+ Slogf.v(TAG, "serviceDied(" + cookie + ")");
synchronized (mLock) {
int moduleId = (int) cookie;
RadioModule prevModule = mModules.remove(moduleId);
@@ -114,7 +114,7 @@ public final class BroadcastRadioService {
for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
if (entry.getValue() == moduleId) {
- Slog.i(TAG, "service " + entry.getKey()
+ Slogf.i(TAG, "service " + entry.getKey()
+ " died; removed RadioModule with ID " + moduleId);
return;
}
@@ -128,12 +128,12 @@ public final class BroadcastRadioService {
try {
IServiceManager manager = IServiceManager.getService();
if (manager == null) {
- Slog.e(TAG, "failed to get HIDL Service Manager");
+ Slogf.e(TAG, "failed to get HIDL Service Manager");
return;
}
manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
} catch (RemoteException ex) {
- Slog.e(TAG, "failed to register for service notifications: ", ex);
+ Slogf.e(TAG, "failed to register for service notifications: ", ex);
}
}
@@ -144,12 +144,12 @@ public final class BroadcastRadioService {
try {
manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to register for service notifications: ", ex);
+ Slogf.e(TAG, "Failed to register for service notifications: ", ex);
}
}
public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
- Slog.v(TAG, "List HIDL 2.0 modules");
+ Slogf.v(TAG, "List HIDL 2.0 modules");
synchronized (mLock) {
return mModules.values().stream().map(module -> module.getProperties())
.collect(Collectors.toList());
@@ -170,7 +170,7 @@ public final class BroadcastRadioService {
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
- Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
+ Slogf.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
throw new IllegalStateException("Cannot open session for non-current user");
@@ -198,7 +198,7 @@ public final class BroadcastRadioService {
public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull IAnnouncementListener listener) {
- Slog.v(TAG, "Add announcementListener");
+ Slogf.v(TAG, "Add announcementListener");
AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
boolean anySupported = false;
synchronized (mLock) {
@@ -207,12 +207,12 @@ public final class BroadcastRadioService {
aggregator.watchModule(module, enabledTypes);
anySupported = true;
} catch (UnsupportedOperationException ex) {
- Slog.v(TAG, "Announcements not supported for this module", ex);
+ Slogf.v(TAG, "Announcements not supported for this module", ex);
}
}
}
if (!anySupported) {
- Slog.i(TAG, "There are no HAL modules that support announcements");
+ Slogf.i(TAG, "There are no HAL modules that support announcements");
}
return aggregator;
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index fb1138f4bc24..34bfa6cb2d46 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -37,21 +37,22 @@ import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
import android.os.ParcelableException;
-import android.util.Slog;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
-class Convert {
+final class Convert {
private static final String TAG = "BcRadio2Srv.convert";
@@ -111,7 +112,7 @@ class Convert {
elem.key = entry.getKey();
elem.value = entry.getValue();
if (elem.key == null || elem.value == null) {
- Slog.w(TAG, "VendorKeyValue contains null pointers");
+ Slogf.w(TAG, "VendorKeyValue contains null pointers");
continue;
}
list.add(elem);
@@ -120,20 +121,21 @@ class Convert {
return list;
}
- static @NonNull Map<String, String>
- vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
- if (info == null) return Collections.emptyMap();
+ static @NonNull Map<String, String> vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
+ Map<String, String> vendorInfoMap = new ArrayMap<>();
+ if (info == null) {
+ return vendorInfoMap;
+ }
- Map<String, String> map = new HashMap<>();
for (VendorKeyValue kvp : info) {
if (kvp.key == null || kvp.value == null) {
- Slog.w(TAG, "VendorKeyValue contains null pointers");
+ Slogf.w(TAG, "VendorKeyValue contains null pointers");
continue;
}
- map.put(kvp.key, kvp.value);
+ vendorInfoMap.put(kvp.key, kvp.value);
}
- return map;
+ return vendorInfoMap;
}
private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
@@ -168,7 +170,7 @@ class Convert {
private static @NonNull int[]
identifierTypesToProgramTypes(@NonNull int[] idTypes) {
- Set<Integer> pTypes = new HashSet<>();
+ Set<Integer> pTypes = new ArraySet<>();
for (int idType : idTypes) {
int pType = identifierTypeToProgramType(idType);
@@ -202,7 +204,7 @@ class Convert {
for (AmFmBandRange range : config.ranges) {
FrequencyBand bandType = Utils.getBand(range.lowerBound);
if (bandType == FrequencyBand.UNKNOWN) {
- Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
+ Slogf.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
continue;
}
if (bandType == FrequencyBand.FM) {
@@ -304,7 +306,7 @@ class Convert {
@NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
if (sel.primaryId.type != 0) return false;
if (sel.primaryId.value != 0) return false;
- if (sel.secondaryIds.size() != 0) return false;
+ if (!sel.secondaryIds.isEmpty()) return false;
return true;
}
@@ -319,7 +321,7 @@ class Convert {
return new ProgramSelector(
identifierTypeToProgramType(sel.primaryId.type),
Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
- secondaryIds, null);
+ secondaryIds, /* vendorIds= */ null);
}
private enum MetadataType {
@@ -335,40 +337,40 @@ class Convert {
}
}
- private static final Map<Integer, MetadataDef> metadataKeys;
+ private static final SparseArray<MetadataDef> METADATA_KEYS;
static {
- metadataKeys = new HashMap<>();
- metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef(
+ METADATA_KEYS = new SparseArray<>();
+ METADATA_KEYS.put(MetadataKey.RDS_PS, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS));
- metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RDS_PTY, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY));
- metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RBDS_PTY, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY));
- metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.RDS_RT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT));
- metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_TITLE, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE));
- metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_ARTIST, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST));
- metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.SONG_ALBUM, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM));
- metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.STATION_ICON, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_ICON));
- metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.ALBUM_ART, new MetadataDef(
MetadataType.INT, RadioMetadata.METADATA_KEY_ART));
- metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME));
- metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME));
- metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT));
- metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME));
- metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT));
- metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME));
- metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
+ METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT));
}
@@ -376,9 +378,9 @@ class Convert {
RadioMetadata.Builder builder = new RadioMetadata.Builder();
for (Metadata entry : meta) {
- MetadataDef keyDef = metadataKeys.get(entry.key);
+ MetadataDef keyDef = METADATA_KEYS.get(entry.key);
if (keyDef == null) {
- Slog.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
+ Slogf.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
continue;
}
if (keyDef.type == MetadataType.STRING) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
index 48112c452f02..b8d12280ac05 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
@@ -19,7 +19,8 @@ package com.android.server.broadcastradio.hal2;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
-import android.util.Slog;
+
+import com.android.server.utils.Slogf;
final class RadioEventLogger {
private final String mTag;
@@ -30,11 +31,12 @@ final class RadioEventLogger {
mEventLogger = new LocalLog(loggerQueueSize);
}
+ @SuppressWarnings("AnnotateFormatMethod")
void logRadioEvent(String logFormat, Object... args) {
String log = String.format(logFormat, args);
mEventLogger.log(log);
if (Log.isLoggable(mTag, Log.DEBUG)) {
- Slog.d(mTag, log);
+ Slogf.d(mTag, log);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index a54af2ef6e44..0e11df8282a7 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -39,16 +39,16 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.MutableInt;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.broadcastradio.RadioServiceUserController;
+import com.android.server.utils.Slogf;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -59,12 +59,12 @@ final class RadioModule {
private static final String TAG = "BcRadio2Srv.module";
private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
- @NonNull private final IBroadcastRadio mService;
- @NonNull private final RadioManager.ModuleProperties mProperties;
+ private final IBroadcastRadio mService;
+ private final RadioManager.ModuleProperties mProperties;
private final Object mLock = new Object();
- @NonNull private final Handler mHandler;
- @NonNull private final RadioEventLogger mEventLogger;
+ private final Handler mHandler;
+ private final RadioEventLogger mEventLogger;
@GuardedBy("mLock")
private ITunerSession mHalTunerSession;
@@ -144,7 +144,7 @@ final class RadioModule {
// Collection of active AIDL tuner sessions created through openSession().
@GuardedBy("mLock")
- private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
+ private final Set<TunerSession> mAidlTunerSessions = new ArraySet<>();
@VisibleForTesting
RadioModule(@NonNull IBroadcastRadio service,
@@ -158,10 +158,10 @@ final class RadioModule {
@Nullable
static RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
try {
- Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
+ Slogf.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName);
IBroadcastRadio service = IBroadcastRadio.getService(fqName);
if (service == null) {
- Slog.w(TAG, "No service found for fqName " + fqName);
+ Slogf.w(TAG, "No service found for fqName " + fqName);
return null;
}
@@ -180,7 +180,7 @@ final class RadioModule {
return new RadioModule(service, prop);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to load module " + fqName, ex);
+ Slogf.e(TAG, "Failed to load module " + fqName, ex);
return null;
}
}
@@ -256,8 +256,8 @@ final class RadioModule {
}
if (idTypes == null) {
- idTypes = new HashSet<>(filter.getIdentifierTypes());
- ids = new HashSet<>(filter.getIdentifiers());
+ idTypes = new ArraySet<>(filter.getIdentifierTypes());
+ ids = new ArraySet<>(filter.getIdentifiers());
includeCategories = filter.areCategoriesIncluded();
excludeModifications = filter.areModificationsExcluded();
continue;
@@ -305,7 +305,7 @@ final class RadioModule {
try {
mHalTunerSession.stopProgramListUpdates();
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex);
}
return;
}
@@ -327,7 +327,7 @@ final class RadioModule {
newFilter));
Convert.throwOnError("startProgramListUpdates", halResult);
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);
}
}
@@ -348,7 +348,7 @@ final class RadioModule {
try {
mHalTunerSession.close();
} catch (RemoteException ex) {
- Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
+ Slogf.e(TAG, "mHalTunerSession.close() failed: ", ex);
}
mHalTunerSession = null;
}
@@ -385,18 +385,17 @@ final class RadioModule {
runnable.run(tunerSession.mCallback);
} catch (DeadObjectException ex) {
// The other side died without calling close(), so just purge it from our records.
- Slog.e(TAG, "Removing dead TunerSession");
+ Slogf.e(TAG, "Removing dead TunerSession");
if (deadSessions == null) {
deadSessions = new ArrayList<>();
}
deadSessions.add(tunerSession);
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
+ Slogf.e(TAG, "Failed to invoke ITunerCallback: ", ex);
}
}
if (deadSessions != null) {
- onTunerSessionsClosedLocked(deadSessions.toArray(
- new TunerSession[deadSessions.size()]));
+ onTunerSessionsClosedLocked(deadSessions.toArray(new TunerSession[0]));
}
}
@@ -429,7 +428,7 @@ final class RadioModule {
try {
hwCloseHandle.value.close();
} catch (RemoteException ex) {
- Slog.e(TAG, "Failed closing announcement listener", ex);
+ Slogf.e(TAG, "Failed closing announcement listener", ex);
}
hwCloseHandle.value = null;
}
@@ -447,7 +446,9 @@ final class RadioModule {
rawImage[i] = rawList.get(i);
}
- if (rawImage == null || rawImage.length == 0) return null;
+ if (rawImage.length == 0) {
+ return null;
+ }
return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 978dc01d1219..6d435e38117f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -30,27 +30,25 @@ import android.hardware.radio.RadioManager;
import android.os.Binder;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.MutableBoolean;
import android.util.MutableInt;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.broadcastradio.RadioServiceUserController;
import com.android.server.utils.Slogf;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-class TunerSession extends ITuner.Stub {
+final class TunerSession extends ITuner.Stub {
private static final String TAG = "BcRadio2Srv.session";
- private static final String kAudioDeviceName = "Radio tuner source";
private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
private final Object mLock = new Object();
- @NonNull private final RadioEventLogger mEventLogger;
+ private final RadioEventLogger mEventLogger;
private final RadioModule mModule;
private final ITunerSession mHwSession;
@@ -99,7 +97,7 @@ class TunerSession extends ITuner.Stub {
try {
mCallback.onError(error);
} catch (RemoteException ex) {
- Slog.w(TAG, "mCallback.onError() failed: ", ex);
+ Slogf.w(TAG, "mCallback.onError() failed: ", ex);
}
}
mModule.onTunerSessionClosed(this);
@@ -129,7 +127,7 @@ class TunerSession extends ITuner.Stub {
checkNotClosedLocked();
mDummyConfig = Objects.requireNonNull(config);
}
- Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
+ Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
}
@@ -148,7 +146,7 @@ class TunerSession extends ITuner.Stub {
if (mIsMuted == mute) return;
mIsMuted = mute;
}
- Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
+ Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
}
@Override
@@ -205,7 +203,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void cancel() {
- Slog.i(TAG, "Cancel");
+ Slogf.i(TAG, "Cancel");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG, "Cannot cancel on HAL 2.0 client from non-current user");
return;
@@ -218,7 +216,8 @@ class TunerSession extends ITuner.Stub {
@Override
public void cancelAnnouncement() {
- Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
+ Slogf.w(TAG,
+ "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
}
@Override
@@ -229,7 +228,7 @@ class TunerSession extends ITuner.Stub {
@Override
public boolean startBackgroundScan() {
- Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+ Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG,
"Cannot start background scan on HAL 2.0 client from non-current user");
@@ -240,7 +239,7 @@ class TunerSession extends ITuner.Stub {
}
@Override
- public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
+ public void startProgramListUpdates(ProgramList.Filter filter) {
mEventLogger.logRadioEvent("start programList updates %s", filter);
if (!RadioServiceUserController.isCurrentOrSystemUser()) {
Slogf.w(TAG,
@@ -250,8 +249,8 @@ class TunerSession extends ITuner.Stub {
// If the AIDL client provides a null filter, it wants all updates, so use the most broad
// filter.
if (filter == null) {
- filter = new ProgramList.Filter(new HashSet<Integer>(),
- new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false);
+ filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+ /* includeCategories= */ true, /* excludeModifications= */ false);
}
synchronized (mLock) {
checkNotClosedLocked();
@@ -285,7 +284,7 @@ class TunerSession extends ITuner.Stub {
if (mProgramInfoCache == null) {
return;
}
- clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, true);
+ clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, /* purge= */ true);
}
dispatchClientUpdateChunks(clientUpdateChunks);
}
@@ -298,7 +297,7 @@ class TunerSession extends ITuner.Stub {
try {
mCallback.onProgramListUpdated(chunk);
} catch (RemoteException ex) {
- Slog.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex);
+ Slogf.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex);
}
}
}
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
index ae79fc0e2c2d..43f3f0c5f3e8 100644
--- a/services/core/java/com/android/server/devicestate/OWNERS
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -1,3 +1,4 @@
ogunwale@google.com
akulian@google.com
-darryljohnson@google.com
+lihongyu@google.com
+kennethford@google.com
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 4aab9d26dbcb..06dc7f54dd8b 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -55,6 +55,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -673,14 +674,10 @@ public class AutomaticBrightnessController {
}
pw.println();
- pw.println(" mAmbientBrightnessThresholds=");
- mAmbientBrightnessThresholds.dump(pw);
- pw.println(" mScreenBrightnessThresholds=");
- mScreenBrightnessThresholds.dump(pw);
- pw.println(" mScreenBrightnessThresholdsIdle=");
- mScreenBrightnessThresholdsIdle.dump(pw);
- pw.println(" mAmbientBrightnessThresholdsIdle=");
- mAmbientBrightnessThresholdsIdle.dump(pw);
+ pw.println(" mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds);
+ pw.println(" mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle);
+ pw.println(" mScreenBrightnessThresholds=" + mScreenBrightnessThresholds);
+ pw.println(" mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle);
}
public float[] getLastSensorValues() {
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 10030b3c9176..dc0e80c686a8 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -117,6 +117,7 @@ class BrightnessRangeController {
() -> mNormalBrightnessModeController.setAutoBrightnessState(state),
() -> mHbmController.setAutoBrightnessEnabled(state)
);
+ mHdrClamper.setAutoBrightnessState(state);
}
void onBrightnessChanged(float brightness, float unthrottledBrightness,
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 70668cbea719..85a231fafb0a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,7 +33,6 @@ import android.os.Environment;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.MathUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Spline;
@@ -46,7 +45,6 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.config.AutoBrightness;
import com.android.server.display.config.BlockingZoneConfig;
import com.android.server.display.config.BrightnessLimitMap;
-import com.android.server.display.config.BrightnessThresholds;
import com.android.server.display.config.BrightnessThrottlingMap;
import com.android.server.display.config.BrightnessThrottlingPoint;
import com.android.server.display.config.Density;
@@ -58,6 +56,7 @@ import com.android.server.display.config.EvenDimmerBrightnessData;
import com.android.server.display.config.HbmTiming;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeout;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
@@ -80,7 +79,6 @@ import com.android.server.display.config.SdrHdrRatioPoint;
import com.android.server.display.config.SensorData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.ThresholdPoint;
import com.android.server.display.config.UsiVersion;
import com.android.server.display.config.XmlParser;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -625,13 +623,6 @@ public class DisplayDeviceConfig {
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
- private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
- private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
- private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
- private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
- private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
- private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
-
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
@@ -713,38 +704,16 @@ public class DisplayDeviceConfig {
private long mBrightnessRampIncreaseMaxIdleMillis = 0;
private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
- private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is
- private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in
- private float mScreenDarkeningMinThreshold = 0.0f; // screen brightness or ambient
- private float mScreenDarkeningMinThresholdIdle = 0.0f; // brightness.
- private float mAmbientLuxBrighteningMinThreshold = 0.0f;
- private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
- private float mAmbientLuxDarkeningMinThreshold = 0.0f;
- private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
-
- // Screen brightness thresholds levels & percentages
- private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
- private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
- // Screen brightness thresholds levels & percentages for idle mode
- private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
- private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
- private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
- // Ambient brightness thresholds levels & percentages
- private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
- private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
-
- // Ambient brightness thresholds levels & percentages for idle mode
- private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
- private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
- private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+
+ // Hysteresis levels for screen/ambient brightness for normal/idle modes
+ private HysteresisLevels mScreenBrightnessHysteresis =
+ HysteresisLevels.loadDisplayBrightnessConfig(null, null);
+ private HysteresisLevels mScreenBrightnessIdleHysteresis =
+ HysteresisLevels.loadDisplayBrightnessIdleConfig(null, null);
+ private HysteresisLevels mAmbientBrightnessHysteresis =
+ HysteresisLevels.loadAmbientBrightnessConfig(null, null);
+ private HysteresisLevels mAmbientBrightnessIdleHysteresis =
+ HysteresisLevels.loadAmbientBrightnessIdleConfig(null, null);
// A mapping between screen off sensor values and lux values
private int[] mScreenOffBrightnessSensorValueToLux;
@@ -1302,324 +1271,20 @@ public class DisplayDeviceConfig {
return mAmbientHorizonShort;
}
- /**
- * The minimum value for the screen brightness increase to actually occur.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenBrighteningMinThreshold() {
- return mScreenBrighteningMinThreshold;
- }
-
- /**
- * The minimum value for the screen brightness decrease to actually occur.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenDarkeningMinThreshold() {
- return mScreenDarkeningMinThreshold;
- }
-
- /**
- * The minimum value for the screen brightness increase to actually occur while in idle screen
- * brightness mode.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenBrighteningMinThresholdIdle() {
- return mScreenBrighteningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the screen brightness decrease to actually occur while in idle screen
- * brightness mode.
- * @return float value in brightness scale of 0 - 1.
- */
- public float getScreenDarkeningMinThresholdIdle() {
- return mScreenDarkeningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the ambient lux increase for a screen brightness change to actually
- * occur.
- * @return float value in lux.
- */
- public float getAmbientLuxBrighteningMinThreshold() {
- return mAmbientLuxBrighteningMinThreshold;
- }
-
- /**
- * The minimum value for the ambient lux decrease for a screen brightness change to actually
- * occur.
- * @return float value in lux.
- */
- public float getAmbientLuxDarkeningMinThreshold() {
- return mAmbientLuxDarkeningMinThreshold;
- }
-
- /**
- * The minimum value for the ambient lux increase for a screen brightness change to actually
- * occur while in idle screen brightness mode.
- * @return float value in lux.
- */
- public float getAmbientLuxBrighteningMinThresholdIdle() {
- return mAmbientLuxBrighteningMinThresholdIdle;
- }
-
- /**
- * The minimum value for the ambient lux decrease for a screen brightness change to actually
- * occur while in idle screen brightness mode.
- * @return float value in lux.
- */
- public float getAmbientLuxDarkeningMinThresholdIdle() {
- return mAmbientLuxDarkeningMinThresholdIdle;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenBrighteningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n]
- * level[MAX] <= value = mScreenBrighteningPercentages[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenBrighteningPercentages applies
- */
- public float[] getScreenBrighteningLevels() {
- return mScreenBrighteningLevels;
- }
-
- /**
- * The array that describes the screen brightening threshold percentage change at each screen
- * brightness level described in mScreenBrighteningLevels.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change
- */
- public float[] getScreenBrighteningPercentages() {
- return mScreenBrighteningPercentages;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenDarkeningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n]
- * level[MAX] <= value = mScreenDarkeningPercentages[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenDarkeningPercentages applies
- */
- public float[] getScreenDarkeningLevels() {
- return mScreenDarkeningLevels;
- }
-
- /**
- * The array that describes the screen darkening threshold percentage change at each screen
- * brightness level described in mScreenDarkeningLevels.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change
- */
- public float[] getScreenDarkeningPercentages() {
- return mScreenDarkeningPercentages;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold
- * percentage applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientBrighteningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n]
- * level[MAX] <= value = mAmbientBrighteningPercentages[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientBrighteningPercentages applies
- */
- public float[] getAmbientBrighteningLevels() {
- return mAmbientBrighteningLevels;
- }
-
- /**
- * The array that describes the ambient brightening threshold percentage change at each ambient
- * brightness level described in mAmbientBrighteningLevels.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change
- */
- public float[] getAmbientBrighteningPercentages() {
- return mAmbientBrighteningPercentages;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientDarkeningLevels
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n]
- * level[MAX] <= value = mAmbientDarkeningPercentages[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientDarkeningPercentages applies
- */
- public float[] getAmbientDarkeningLevels() {
- return mAmbientDarkeningLevels;
- }
-
- /**
- * The array that describes the ambient darkening threshold percentage change at each ambient
- * brightness level described in mAmbientDarkeningLevels.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change
- */
- public float[] getAmbientDarkeningPercentages() {
- return mAmbientDarkeningPercentages;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenBrighteningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n]
- * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenBrighteningPercentagesIdle applies
- */
- public float[] getScreenBrighteningLevelsIdle() {
- return mScreenBrighteningLevelsIdle;
- }
-
- /**
- * The array that describes the screen brightening threshold percentage change at each screen
- * brightness level described in mScreenBrighteningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of brightness increase required in order for the
- * screen brightness to change while in idle mode.
- */
- public float[] getScreenBrighteningPercentagesIdle() {
- return mScreenBrighteningPercentagesIdle;
- }
-
- /**
- * The array that describes the range of screen brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current screen brightness value
- * level = mScreenDarkeningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n]
- * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX]
- *
- * @return the screen brightness levels between 0.0 and 1.0 for which each
- * mScreenDarkeningPercentagesIdle applies
- */
- public float[] getScreenDarkeningLevelsIdle() {
- return mScreenDarkeningLevelsIdle;
+ public HysteresisLevels getAmbientBrightnessHysteresis() {
+ return mAmbientBrightnessHysteresis;
}
- /**
- * The array that describes the screen darkening threshold percentage change at each screen
- * brightness level described in mScreenDarkeningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of brightness decrease required in order for the
- * screen brightness to change while in idle mode.
- */
- public float[] getScreenDarkeningPercentagesIdle() {
- return mScreenDarkeningPercentagesIdle;
+ public HysteresisLevels getAmbientBrightnessIdleHysteresis() {
+ return mAmbientBrightnessIdleHysteresis;
}
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientBrighteningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n]
- * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientBrighteningPercentagesIdle applies
- */
- public float[] getAmbientBrighteningLevelsIdle() {
- return mAmbientBrighteningLevelsIdle;
+ public HysteresisLevels getScreenBrightnessHysteresis() {
+ return mScreenBrightnessHysteresis;
}
- /**
- * The array that describes the ambient brightness threshold percentage change whilst in
- * idle screen brightness mode at each ambient brightness level described in
- * mAmbientBrighteningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of ambient brightness increase required in order
- * for the screen brightness to change
- */
- public float[] getAmbientBrighteningPercentagesIdle() {
- return mAmbientBrighteningPercentagesIdle;
- }
-
- /**
- * The array that describes the range of ambient brightness that each threshold percentage
- * applies within whilst in idle screen brightness mode.
- *
- * The (zero-based) index is calculated as follows
- * value = current ambient brightness value
- * level = mAmbientDarkeningLevelsIdle
- *
- * condition return
- * value < level[0] = 0.0f
- * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n]
- * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX]
- *
- * @return the ambient brightness levels from 0 lux upwards for which each
- * mAmbientDarkeningPercentagesIdle applies
- */
- public float[] getAmbientDarkeningLevelsIdle() {
- return mAmbientDarkeningLevelsIdle;
- }
-
- /**
- * The array that describes the ambient brightness threshold percentage change whilst in
- * idle screen brightness mode at each ambient brightness level described in
- * mAmbientDarkeningLevelsIdle.
- *
- * @return the percentages between 0 and 100 of ambient brightness decrease required in order
- * for the screen brightness to change
- */
- public float[] getAmbientDarkeningPercentagesIdle() {
- return mAmbientDarkeningPercentagesIdle;
+ public HysteresisLevels getScreenBrightnessIdleHysteresis() {
+ return mScreenBrightnessIdleHysteresis;
}
public SensorData getAmbientLightSensor() {
@@ -1985,49 +1650,13 @@ public class DisplayDeviceConfig {
+ "mAmbientHorizonLong=" + mAmbientHorizonLong
+ ", mAmbientHorizonShort=" + mAmbientHorizonShort
+ "\n"
- + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
- + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
- + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
- + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
- + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
- + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle
- + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
- + ", mAmbientLuxBrighteningMinThresholdIdle="
- + mAmbientLuxBrighteningMinThresholdIdle
+ + "mAmbientBrightnessHysteresis=" + mAmbientBrightnessHysteresis
+ + "\n"
+ + "mAmbientIdleHysteresis=" + mAmbientBrightnessIdleHysteresis
+ "\n"
- + "mScreenBrighteningLevels=" + Arrays.toString(
- mScreenBrighteningLevels)
- + ", mScreenBrighteningPercentages=" + Arrays.toString(
- mScreenBrighteningPercentages)
- + ", mScreenDarkeningLevels=" + Arrays.toString(
- mScreenDarkeningLevels)
- + ", mScreenDarkeningPercentages=" + Arrays.toString(
- mScreenDarkeningPercentages)
- + ", mAmbientBrighteningLevels=" + Arrays.toString(
- mAmbientBrighteningLevels)
- + ", mAmbientBrighteningPercentages=" + Arrays.toString(
- mAmbientBrighteningPercentages)
- + ", mAmbientDarkeningLevels=" + Arrays.toString(
- mAmbientDarkeningLevels)
- + ", mAmbientDarkeningPercentages=" + Arrays.toString(
- mAmbientDarkeningPercentages)
+ + "mScreenBrightnessHysteresis=" + mScreenBrightnessHysteresis
+ "\n"
- + "mAmbientBrighteningLevelsIdle=" + Arrays.toString(
- mAmbientBrighteningLevelsIdle)
- + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
- mAmbientBrighteningPercentagesIdle)
- + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
- mAmbientDarkeningLevelsIdle)
- + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
- mAmbientDarkeningPercentagesIdle)
- + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
- mScreenBrighteningLevelsIdle)
- + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
- mScreenBrighteningPercentagesIdle)
- + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
- mScreenDarkeningLevelsIdle)
- + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
- mScreenDarkeningPercentagesIdle)
+ + "mScreenBrightnessIdleHysteresis=" + mScreenBrightnessIdleHysteresis
+ "\n"
+ "mAmbientLightSensor=" + mAmbientLightSensor
+ ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
@@ -3137,281 +2766,15 @@ public class DisplayDeviceConfig {
}
private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
- loadDisplayBrightnessThresholds(config);
- loadAmbientBrightnessThresholds(config);
- loadDisplayBrightnessThresholdsIdle(config);
- loadAmbientBrightnessThresholdsIdle(config);
- }
-
- private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
- BrightnessThresholds brighteningScreen = null;
- BrightnessThresholds darkeningScreen = null;
- if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
- brighteningScreen =
- config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
- darkeningScreen =
- config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
-
- }
-
- // Screen bright/darkening threshold levels for active mode
- Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningScreen,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenBrighteningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
-
- mScreenBrighteningLevels = screenBrighteningPair.first;
- mScreenBrighteningPercentages = screenBrighteningPair.second;
-
- Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningScreen,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenDarkeningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenDarkeningLevels = screenDarkeningPair.first;
- mScreenDarkeningPercentages = screenDarkeningPair.second;
-
- // Screen bright/darkening threshold minimums for active mode
- if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
- mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
- }
- if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
- mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
- }
- }
-
- private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
- // Ambient Brightness Threshold Levels
- BrightnessThresholds brighteningAmbientLux = null;
- BrightnessThresholds darkeningAmbientLux = null;
- if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
- brighteningAmbientLux =
- config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
- darkeningAmbientLux =
- config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
- }
-
- // Ambient bright/darkening threshold levels for active mode
- Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningAmbientLux,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientBrighteningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
- mAmbientBrighteningLevels = ambientBrighteningPair.first;
- mAmbientBrighteningPercentages = ambientBrighteningPair.second;
-
- Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningAmbientLux,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientDarkeningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
- mAmbientDarkeningLevels = ambientDarkeningPair.first;
- mAmbientDarkeningPercentages = ambientDarkeningPair.second;
-
- // Ambient bright/darkening threshold minimums for active/idle mode
- if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
- mAmbientLuxBrighteningMinThreshold =
- brighteningAmbientLux.getMinimum().floatValue();
- }
-
- if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
- mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
- }
- }
-
- private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
- BrightnessThresholds brighteningScreenIdle = null;
- BrightnessThresholds darkeningScreenIdle = null;
- if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
- brighteningScreenIdle =
- config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
- darkeningScreenIdle =
- config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
- }
-
- Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningScreenIdle,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenBrighteningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
- mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
-
- Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningScreenIdle,
- com.android.internal.R.array.config_screenThresholdLevels,
- com.android.internal.R.array.config_screenDarkeningThresholds,
- DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
- /* potentialOldBrightnessScale= */ true);
- mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
- mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
-
- if (brighteningScreenIdle != null
- && brighteningScreenIdle.getMinimum() != null) {
- mScreenBrighteningMinThresholdIdle =
- brighteningScreenIdle.getMinimum().floatValue();
- }
- if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
- mScreenDarkeningMinThresholdIdle =
- darkeningScreenIdle.getMinimum().floatValue();
- }
- }
-
- private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
- BrightnessThresholds brighteningAmbientLuxIdle = null;
- BrightnessThresholds darkeningAmbientLuxIdle = null;
- if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
- brighteningAmbientLuxIdle =
- config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
- darkeningAmbientLuxIdle =
- config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
- }
-
- Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
- brighteningAmbientLuxIdle,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientBrighteningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
- mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
- mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
-
- Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
- darkeningAmbientLuxIdle,
- com.android.internal.R.array.config_ambientThresholdLevels,
- com.android.internal.R.array.config_ambientDarkeningThresholds,
- DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
- mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
- mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
-
- if (brighteningAmbientLuxIdle != null
- && brighteningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxBrighteningMinThresholdIdle =
- brighteningAmbientLuxIdle.getMinimum().floatValue();
- }
-
- if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
- mAmbientLuxDarkeningMinThresholdIdle =
- darkeningAmbientLuxIdle.getMinimum().floatValue();
- }
- }
-
- private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
- int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
- float[] defaultPercentage) {
- return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
- configFallbackPercentage, defaultLevels, defaultPercentage, false);
- }
-
- // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
- // percentages for brightness levels at or above the lux value.
- // Historically, config.xml would have an array for brightness levels that was 1 shorter than
- // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
- // rest of the framework. Values were also defined in different units (permille vs percent).
- private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
- int configFallbackThreshold, int configFallbackPermille,
- float[] defaultLevels, float[] defaultPercentage,
- boolean potentialOldBrightnessScale) {
- if (thresholds != null
- && thresholds.getBrightnessThresholdPoints() != null
- && thresholds.getBrightnessThresholdPoints()
- .getBrightnessThresholdPoint().size() != 0) {
-
- // The level and percentages arrays are equal length in the ddc (new system)
- List<ThresholdPoint> points =
- thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
- final int size = points.size();
-
- float[] thresholdLevels = new float[size];
- float[] thresholdPercentages = new float[size];
-
- int i = 0;
- for (ThresholdPoint point : points) {
- thresholdLevels[i] = point.getThreshold().floatValue();
- thresholdPercentages[i] = point.getPercentage().floatValue();
- i++;
- }
- return new Pair<>(thresholdLevels, thresholdPercentages);
- } else {
- // The level and percentages arrays are unequal length in config.xml (old system)
- // We prefix the array with a 0 value to ensure they can be handled consistently
- // with the new system.
-
- // Load levels array
- int[] configThresholdArray = mContext.getResources().getIntArray(
- configFallbackThreshold);
- int configThresholdsSize;
- if (configThresholdArray == null || configThresholdArray.length == 0) {
- configThresholdsSize = 1;
- } else {
- configThresholdsSize = configThresholdArray.length + 1;
- }
-
-
- // Load percentage array
- int[] configPermille = mContext.getResources().getIntArray(
- configFallbackPermille);
-
- // Ensure lengths match up
- boolean emptyArray = configPermille == null || configPermille.length == 0;
- if (emptyArray && configThresholdsSize == 1) {
- return new Pair<>(defaultLevels, defaultPercentage);
- }
- if (emptyArray || configPermille.length != configThresholdsSize) {
- throw new IllegalArgumentException(
- "Brightness threshold arrays do not align in length");
- }
-
- // Calculate levels array
- float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
- // Start at 1, so that 0 index value is 0.0f (default)
- for (int i = 1; i < configThresholdsSize; i++) {
- configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
- }
- if (potentialOldBrightnessScale) {
- configThresholdWithZeroPrefixed =
- constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
- }
-
- // Calculate percentages array
- float[] configPercentage = new float[configThresholdsSize];
- for (int i = 0; i < configPermille.length; i++) {
- configPercentage[i] = configPermille[i] / 10.0f;
- } return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
- }
- }
-
- /**
- * This check is due to historical reasons, where screen thresholdLevels used to be
- * integer values in the range of [0-255], but then was changed to be float values from [0,1].
- * To accommodate both the possibilities, we first check if all the thresholdLevels are in
- * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
- */
- private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
- if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
- /* maxValueInclusive= */ 1.0f)) {
- return thresholdLevels;
- }
-
- Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
- float[] thresholdLevelsScaled = new float[thresholdLevels.length];
- for (int index = 0; thresholdLevels.length > index; ++index) {
- thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
- }
- return thresholdLevelsScaled;
- }
-
- private boolean isAllInRange(float[] configArray, float minValueInclusive,
- float maxValueInclusive) {
- for (float v : configArray) {
- if (v < minValueInclusive || v > maxValueInclusive) {
- return false;
- }
- }
- return true;
+ Resources res = mContext.getResources();
+ mScreenBrightnessHysteresis =
+ HysteresisLevels.loadDisplayBrightnessConfig(config, res);
+ mScreenBrightnessIdleHysteresis =
+ HysteresisLevels.loadDisplayBrightnessIdleConfig(config, res);
+ mAmbientBrightnessHysteresis =
+ HysteresisLevels.loadAmbientBrightnessConfig(config, res);
+ mAmbientBrightnessIdleHysteresis =
+ HysteresisLevels.loadAmbientBrightnessIdleConfig(config, res);
}
private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 40f0362ff8f3..31092f27c838 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -757,6 +757,7 @@ public final class DisplayManagerService extends SystemService {
mContext.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mHandler), new DeviceStateListener());
+ mLogicalDisplayMapper.onWindowManagerReady();
scheduleTraversalLocked(false);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b21a89a92803..404c3ad3a51b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -86,6 +86,7 @@ import com.android.server.display.brightness.clamper.BrightnessClamperController
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.state.DisplayStateController;
@@ -1050,76 +1051,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (defaultModeBrightnessMapper != null) {
// Ambient Lux - Active Mode Brightness Thresholds
- float[] ambientBrighteningThresholds =
- mDisplayDeviceConfig.getAmbientBrighteningPercentages();
- float[] ambientDarkeningThresholds =
- mDisplayDeviceConfig.getAmbientDarkeningPercentages();
- float[] ambientBrighteningLevels =
- mDisplayDeviceConfig.getAmbientBrighteningLevels();
- float[] ambientDarkeningLevels =
- mDisplayDeviceConfig.getAmbientDarkeningLevels();
- float ambientDarkeningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
- float ambientBrighteningMinThreshold =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
- HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
- ambientBrighteningThresholds, ambientDarkeningThresholds,
- ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
- ambientBrighteningMinThreshold);
+ HysteresisLevels ambientBrightnessThresholds =
+ mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
// Display - Active Mode Brightness Thresholds
- float[] screenBrighteningThresholds =
- mDisplayDeviceConfig.getScreenBrighteningPercentages();
- float[] screenDarkeningThresholds =
- mDisplayDeviceConfig.getScreenDarkeningPercentages();
- float[] screenBrighteningLevels =
- mDisplayDeviceConfig.getScreenBrighteningLevels();
- float[] screenDarkeningLevels =
- mDisplayDeviceConfig.getScreenDarkeningLevels();
- float screenDarkeningMinThreshold =
- mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
- float screenBrighteningMinThreshold =
- mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
- HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
- screenBrighteningThresholds, screenDarkeningThresholds,
- screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
- screenBrighteningMinThreshold, true);
+ HysteresisLevels screenBrightnessThresholds =
+ mDisplayDeviceConfig.getScreenBrightnessHysteresis();
// Ambient Lux - Idle Screen Brightness Thresholds
- float ambientDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
- float ambientBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
- float[] ambientBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
- float[] ambientDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
- float[] ambientBrighteningLevelsIdle =
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
- float[] ambientDarkeningLevelsIdle =
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
- HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
- ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
- ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+ HysteresisLevels ambientBrightnessThresholdsIdle =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
// Display - Idle Screen Brightness Thresholds
- float screenDarkeningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
- float screenBrighteningMinThresholdIdle =
- mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
- float[] screenBrighteningThresholdsIdle =
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
- float[] screenDarkeningThresholdsIdle =
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
- float[] screenBrighteningLevelsIdle =
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
- float[] screenDarkeningLevelsIdle =
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
- HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
- screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
- screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
- screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+ HysteresisLevels screenBrightnessThresholdsIdle =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
long brighteningLightDebounce = mDisplayDeviceConfig
.getAutoBrightnessBrighteningLightDebounce();
@@ -3208,25 +3153,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
}
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
- }
-
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return new HysteresisLevels(brighteningThresholdsPercentages,
- darkeningThresholdsPercentages, brighteningThresholdLevels,
- darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
- potentialOldBrightnessRange);
- }
-
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager,
Sensor lightSensor,
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
deleted file mode 100644
index 0521b8ac4f3b..000000000000
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.util.Slog;
-
-import com.android.server.display.utils.DebugUtils;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-/**
- * A helper class for handling access to illuminance hysteresis level values.
- */
-public class HysteresisLevels {
- private static final String TAG = "HysteresisLevels";
-
- // To enable these logs, run:
- // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
- private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
- private final float[] mBrighteningThresholdsPercentages;
- private final float[] mDarkeningThresholdsPercentages;
- private final float[] mBrighteningThresholdLevels;
- private final float[] mDarkeningThresholdLevels;
- private final float mMinDarkening;
- private final float mMinBrightening;
-
- /**
- * Creates a {@code HysteresisLevels} object with the given equal-length
- * float arrays.
- * @param brighteningThresholdsPercentages 0-100 of thresholds
- * @param darkeningThresholdsPercentages 0-100 of thresholds
- * @param brighteningThresholdLevels float array of brightness values in the relevant units
- * @param minBrighteningThreshold the minimum value for which the brightening value needs to
- * return.
- * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
- * @param potentialOldBrightnessRange whether or not the values used could be from the old
- * screen brightness range ie, between 1-255.
- */
- HysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages,
- float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
- float minDarkeningThreshold, float minBrighteningThreshold,
- boolean potentialOldBrightnessRange) {
- if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
- || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
- throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
- }
- mBrighteningThresholdsPercentages =
- setArrayFormat(brighteningThresholdsPercentages, 100.0f);
- mDarkeningThresholdsPercentages =
- setArrayFormat(darkeningThresholdsPercentages, 100.0f);
- mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
- mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
- mMinDarkening = minDarkeningThreshold;
- mMinBrightening = minBrighteningThreshold;
- }
-
- HysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages,
- float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
- float minDarkeningThreshold, float minBrighteningThreshold) {
- this(brighteningThresholdsPercentages, darkeningThresholdsPercentages,
- brighteningThresholdLevels, darkeningThresholdLevels, minDarkeningThreshold,
- minBrighteningThreshold, false);
- }
-
- /**
- * Return the brightening hysteresis threshold for the given value level.
- */
- public float getBrighteningThreshold(float value) {
- final float brightConstant = getReferenceLevel(value,
- mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
-
- float brightThreshold = value * (1.0f + brightConstant);
- if (DEBUG) {
- Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
- + brightThreshold + ", value=" + value);
- }
-
- brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
- return brightThreshold;
- }
-
- /**
- * Return the darkening hysteresis threshold for the given value level.
- */
- public float getDarkeningThreshold(float value) {
- final float darkConstant = getReferenceLevel(value,
- mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
- float darkThreshold = value * (1.0f - darkConstant);
- if (DEBUG) {
- Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
- + darkThreshold + ", value=" + value);
- }
- darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
- return Math.max(darkThreshold, 0.0f);
- }
-
- /**
- * Return the hysteresis constant for the closest threshold value from the given array.
- */
- private float getReferenceLevel(float value, float[] thresholdLevels,
- float[] thresholdPercentages) {
- if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
- return 0.0f;
- }
- int index = 0;
- while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
- index++;
- }
- return thresholdPercentages[index];
- }
-
- /**
- * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
- */
- private float[] setArrayFormat(float[] configArray, float divideFactor) {
- float[] levelArray = new float[configArray.length];
- for (int index = 0; levelArray.length > index; ++index) {
- levelArray[index] = configArray[index] / divideFactor;
- }
- return levelArray;
- }
-
- void dump(PrintWriter pw) {
- pw.println("HysteresisLevels");
- pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
- pw.println(" mBrighteningThresholdsPercentages="
- + Arrays.toString(mBrighteningThresholdsPercentages));
- pw.println(" mMinBrightening=" + mMinBrightening);
- pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
- pw.println(" mDarkeningThresholdsPercentages="
- + Arrays.toString(mDarkeningThresholdsPercentages));
- pw.println(" mMinDarkening=" + mMinDarkening);
- }
-}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 6203a32151a0..bca53cf02f69 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -41,10 +41,12 @@ import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.foldables.FoldGracePeriodProvider;
+import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import com.android.server.display.utils.DebugUtils;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.FoldSettingProvider;
import java.io.PrintWriter;
@@ -189,6 +191,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
* #updateLogicalDisplaysLocked} to establish which Virtual Devices own which Virtual Displays.
*/
private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
+ private WindowManagerPolicy mWindowManagerPolicy;
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
private final DisplayIdProducer mIdProducer = (isDefault) ->
@@ -274,6 +277,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mListener.onTraversalRequested();
}
+ public void onWindowManagerReady() {
+ mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
+ }
+
public LogicalDisplay getDisplayLocked(int displayId) {
return getDisplayLocked(displayId, /* includeDisabled= */ true);
}
@@ -1114,14 +1121,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
+ boolean newDisplayCreated = false;
if (newDisplay == null) {
newDisplay = createNewLogicalDisplayLocked(
null /*displayDevice*/, logicalDisplayId);
+ newDisplayCreated = true;
}
// Now swap the underlying display devices between the old display and the new display
final LogicalDisplay oldDisplay = getDisplayLocked(device);
if (newDisplay != oldDisplay) {
+ // Display is swapping, notify WindowManager, so it can prepare for
+ // the display switch
+ if (!newDisplayCreated && mWindowManagerPolicy != null) {
+ mWindowManagerPolicy.onDisplaySwitchStart(newDisplay.getDisplayIdLocked());
+ }
+
newDisplay.swapDisplaysLocked(oldDisplay);
}
DisplayDeviceConfig config = device.getDisplayDeviceConfig();
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index 135ebd8f4fbf..e94cf00437eb 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -79,10 +79,12 @@ class NormalBrightnessModeController {
maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE);
}
- if (maxBrightnessPoints == null) {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // Temporary disabling this Controller if auto brightness is off, to avoid capping
+ // brightness based on stale ambient lux. The issue is tracked here: b/322445088
+ if (mAutoBrightnessEnabled && maxBrightnessPoints == null) {
maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT);
}
-
if (maxBrightnessPoints != null) {
for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) {
float ambientBoundary = brightnessPoint.getKey();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 01a8d360a526..f1cb66c0efbb 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,6 +24,7 @@ import android.os.PowerManager;
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -56,6 +57,8 @@ public class HdrClamper {
private float mTransitionRate = -1f;
private float mDesiredTransitionRate = -1f;
+ private boolean mAutoBrightnessEnabled = false;
+
public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
Handler handler) {
this(clamperChangeListener, handler, new Injector());
@@ -122,6 +125,18 @@ public class HdrClamper {
recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
}
+ /**
+ * Sets state of auto brightness to temporary disabling this Clamper if auto brightness is off.
+ * The issue is tracked here: b/322445088
+ */
+ public void setAutoBrightnessState(int state) {
+ boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ if (isEnabled != mAutoBrightnessEnabled) {
+ mAutoBrightnessEnabled = isEnabled;
+ recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
+ }
+ }
+
/** Clean up all resources */
@SuppressLint("AndroidFrameworkRequiresPermission")
public void stop() {
@@ -145,6 +160,7 @@ public class HdrClamper {
: mHdrBrightnessData.toString()));
pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
}
private void reset() {
@@ -163,7 +179,10 @@ public class HdrClamper {
private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
boolean hdrVisible) {
- if (data == null || !hdrVisible) {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // Temporary disabling this Clamper if auto brightness is off, to avoid capping
+ // brightness based on stale ambient lux. The issue is tracked here: b/322445088
+ if (data == null || !hdrVisible || !mAutoBrightnessEnabled) {
reset();
return;
}
diff --git a/services/core/java/com/android/server/display/config/HysteresisLevels.java b/services/core/java/com/android/server/display/config/HysteresisLevels.java
new file mode 100644
index 000000000000..e659d88c9752
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HysteresisLevels.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.ArrayRes;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.DebugUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A helper class for handling access to illuminance hysteresis level values.
+ */
+public class HysteresisLevels {
+ private static final String TAG = "HysteresisLevels";
+
+ private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+ private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+ private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+ private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+ /**
+ * The array that describes the brightness threshold percentage change
+ * at each brightness level described in mBrighteningThresholdLevels.
+ */
+ private final float[] mBrighteningThresholdsPercentages;
+
+ /**
+ * The array that describes the brightness threshold percentage change
+ * at each brightness level described in mDarkeningThresholdLevels.
+ */
+ private final float[] mDarkeningThresholdsPercentages;
+
+ /**
+ * The array that describes the range of brightness that each threshold percentage applies to
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current brightness value
+ * level = mBrighteningThresholdLevels
+ *
+ * condition return
+ * value < mBrighteningThresholdLevels[0] = 0.0f
+ * level[n] <= value < level[n+1] = mBrighteningThresholdsPercentages[n]
+ * level[MAX] <= value = mBrighteningThresholdsPercentages[MAX]
+ */
+ private final float[] mBrighteningThresholdLevels;
+
+ /**
+ * The array that describes the range of brightness that each threshold percentage applies to
+ *
+ * The (zero-based) index is calculated as follows
+ * value = current brightness value
+ * level = mDarkeningThresholdLevels
+ *
+ * condition return
+ * value < level[0] = 0.0f
+ * level[n] <= value < level[n+1] = mDarkeningThresholdsPercentages[n]
+ * level[MAX] <= value = mDarkeningThresholdsPercentages[MAX]
+ */
+ private final float[] mDarkeningThresholdLevels;
+
+ /**
+ * The minimum value decrease for darkening event
+ */
+ private final float mMinDarkening;
+
+ /**
+ * The minimum value increase for brightening event.
+ */
+ private final float mMinBrightening;
+
+ /**
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * float arrays.
+ *
+ * @param brighteningThresholdsPercentages 0-100 of thresholds
+ * @param darkeningThresholdsPercentages 0-100 of thresholds
+ * @param brighteningThresholdLevels float array of brightness values in the relevant
+ * units
+ * @param minBrighteningThreshold the minimum value for which the brightening value
+ * needs to
+ * return.
+ * @param minDarkeningThreshold the minimum value for which the darkening value needs
+ * to return.
+ */
+ @VisibleForTesting
+ public HysteresisLevels(float[] brighteningThresholdsPercentages,
+ float[] darkeningThresholdsPercentages,
+ float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+ float minDarkeningThreshold, float minBrighteningThreshold) {
+ if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+ || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
+ throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+ }
+ mBrighteningThresholdsPercentages =
+ setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+ mDarkeningThresholdsPercentages =
+ setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+ mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+ mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
+ mMinDarkening = minDarkeningThreshold;
+ mMinBrightening = minBrighteningThreshold;
+ }
+
+ /**
+ * Return the brightening hysteresis threshold for the given value level.
+ */
+ public float getBrighteningThreshold(float value) {
+ final float brightConstant = getReferenceLevel(value,
+ mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
+ float brightThreshold = value * (1.0f + brightConstant);
+ if (DEBUG) {
+ Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+ + brightThreshold + ", value=" + value);
+ }
+
+ brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
+ return brightThreshold;
+ }
+
+ /**
+ * Return the darkening hysteresis threshold for the given value level.
+ */
+ public float getDarkeningThreshold(float value) {
+ final float darkConstant = getReferenceLevel(value,
+ mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
+ float darkThreshold = value * (1.0f - darkConstant);
+ if (DEBUG) {
+ Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+ + darkThreshold + ", value=" + value);
+ }
+ darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+ return Math.max(darkThreshold, 0.0f);
+ }
+
+ @VisibleForTesting
+ public float[] getBrighteningThresholdsPercentages() {
+ return mBrighteningThresholdsPercentages;
+ }
+
+ @VisibleForTesting
+ public float[] getDarkeningThresholdsPercentages() {
+ return mDarkeningThresholdsPercentages;
+ }
+
+ @VisibleForTesting
+ public float[] getBrighteningThresholdLevels() {
+ return mBrighteningThresholdLevels;
+ }
+
+ @VisibleForTesting
+ public float[] getDarkeningThresholdLevels() {
+ return mDarkeningThresholdLevels;
+ }
+
+ @VisibleForTesting
+ public float getMinDarkening() {
+ return mMinDarkening;
+ }
+
+ @VisibleForTesting
+ public float getMinBrightening() {
+ return mMinBrightening;
+ }
+
+ /**
+ * Return the hysteresis constant for the closest threshold value from the given array.
+ */
+ private float getReferenceLevel(float value, float[] thresholdLevels,
+ float[] thresholdPercentages) {
+ if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+ return 0.0f;
+ }
+ int index = 0;
+ while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+ index++;
+ }
+ return thresholdPercentages[index];
+ }
+
+ /**
+ * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
+ */
+ private float[] setArrayFormat(float[] configArray, float divideFactor) {
+ float[] levelArray = new float[configArray.length];
+ for (int index = 0; levelArray.length > index; ++index) {
+ levelArray[index] = configArray[index] / divideFactor;
+ }
+ return levelArray;
+ }
+
+ @Override
+ public String toString() {
+ return "HysteresisLevels {"
+ + "\n"
+ + " mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)
+ + ",\n"
+ + " mBrighteningThresholdsPercentages="
+ + Arrays.toString(mBrighteningThresholdsPercentages)
+ + ",\n"
+ + " mMinBrightening=" + mMinBrightening
+ + ",\n"
+ + " mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels)
+ + ",\n"
+ + " mDarkeningThresholdsPercentages="
+ + Arrays.toString(mDarkeningThresholdsPercentages)
+ + ",\n"
+ + " mMinDarkening=" + mMinDarkening
+ + "\n"
+ + "}";
+ }
+
+ /**
+ * Creates hysteresis levels for Active Ambient Lux
+ */
+ public static HysteresisLevels loadAmbientBrightnessConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getAmbientBrightnessChangeThresholds(),
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+ DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+ DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ false);
+ }
+
+ /**
+ * Creates hysteresis levels for Active Screen Brightness
+ */
+ public static HysteresisLevels loadDisplayBrightnessConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getDisplayBrightnessChangeThresholds(),
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS,
+ DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ true);
+ }
+
+ /**
+ * Creates hysteresis levels for Idle Ambient Lux
+ */
+ public static HysteresisLevels loadAmbientBrightnessIdleConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getAmbientBrightnessChangeThresholdsIdle(),
+ com.android.internal.R.array.config_ambientThresholdLevels,
+ com.android.internal.R.array.config_ambientBrighteningThresholds,
+ com.android.internal.R.array.config_ambientDarkeningThresholds,
+ DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+ DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+ DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ false);
+ }
+
+ /**
+ * Creates hysteresis levels for Idle Screen Brightness
+ */
+ public static HysteresisLevels loadDisplayBrightnessIdleConfig(
+ @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+ return createHysteresisLevels(
+ config == null ? null : config.getDisplayBrightnessChangeThresholdsIdle(),
+ com.android.internal.R.array.config_screenThresholdLevels,
+ com.android.internal.R.array.config_screenBrighteningThresholds,
+ com.android.internal.R.array.config_screenDarkeningThresholds,
+ DEFAULT_SCREEN_THRESHOLD_LEVELS,
+ DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+ DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+ resources, /* potentialOldBrightnessScale= */ true);
+ }
+
+
+ private static HysteresisLevels createHysteresisLevels(
+ @Nullable Thresholds thresholds,
+ @ArrayRes int configLevels,
+ @ArrayRes int configBrighteningThresholds,
+ @ArrayRes int configDarkeningThresholds,
+ float[] defaultLevels,
+ float[] defaultBrighteningThresholds,
+ float[] defaultDarkeningThresholds,
+ @Nullable Resources resources,
+ boolean potentialOldBrightnessScale
+ ) {
+ BrightnessThresholds brighteningThresholds =
+ thresholds == null ? null : thresholds.getBrighteningThresholds();
+ BrightnessThresholds darkeningThresholds =
+ thresholds == null ? null : thresholds.getDarkeningThresholds();
+
+ Pair<float[], float[]> brighteningPair = getBrightnessLevelAndPercentage(
+ brighteningThresholds,
+ configLevels, configBrighteningThresholds,
+ defaultLevels, defaultBrighteningThresholds,
+ potentialOldBrightnessScale, resources);
+
+ Pair<float[], float[]> darkeningPair = getBrightnessLevelAndPercentage(
+ darkeningThresholds,
+ configLevels, configDarkeningThresholds,
+ defaultLevels, defaultDarkeningThresholds,
+ potentialOldBrightnessScale, resources);
+
+ float brighteningMinThreshold =
+ brighteningThresholds != null && brighteningThresholds.getMinimum() != null
+ ? brighteningThresholds.getMinimum().floatValue() : 0f;
+ float darkeningMinThreshold =
+ darkeningThresholds != null && darkeningThresholds.getMinimum() != null
+ ? darkeningThresholds.getMinimum().floatValue() : 0f;
+
+ return new HysteresisLevels(
+ brighteningPair.second,
+ darkeningPair.second,
+ brighteningPair.first,
+ darkeningPair.first,
+ darkeningMinThreshold,
+ brighteningMinThreshold
+ );
+ }
+
+ // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+ // percentages for brightness levels at or above the lux value.
+ // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+ // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+ // rest of the framework. Values were also defined in different units (permille vs percent).
+ private static Pair<float[], float[]> getBrightnessLevelAndPercentage(
+ @Nullable BrightnessThresholds thresholds,
+ int configFallbackThreshold, int configFallbackPermille,
+ float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale,
+ @Nullable Resources resources) {
+ if (thresholds != null
+ && thresholds.getBrightnessThresholdPoints() != null
+ && !thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint()
+ .isEmpty()) {
+
+ // The level and percentages arrays are equal length in the ddc (new system)
+ List<ThresholdPoint> points =
+ thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+ final int size = points.size();
+
+ float[] thresholdLevels = new float[size];
+ float[] thresholdPercentages = new float[size];
+
+ int i = 0;
+ for (ThresholdPoint point : points) {
+ thresholdLevels[i] = point.getThreshold().floatValue();
+ thresholdPercentages[i] = point.getPercentage().floatValue();
+ i++;
+ }
+ return new Pair<>(thresholdLevels, thresholdPercentages);
+ } else if (resources != null) {
+ // The level and percentages arrays are unequal length in config.xml (old system)
+ // We prefix the array with a 0 value to ensure they can be handled consistently
+ // with the new system.
+
+ // Load levels array
+ int[] configThresholdArray = resources.getIntArray(configFallbackThreshold);
+ int configThresholdsSize;
+ // null check is not needed here, however it test we are mocking resources that might
+ // return null
+ if (configThresholdArray == null || configThresholdArray.length == 0) {
+ configThresholdsSize = 1;
+ } else {
+ configThresholdsSize = configThresholdArray.length + 1;
+ }
+
+ // Load percentage array
+ int[] configPermille = resources.getIntArray(configFallbackPermille);
+
+ // Ensure lengths match up
+ // null check is not needed here, however it test we are mocking resources that might
+ // return null
+ boolean emptyArray = configPermille == null || configPermille.length == 0;
+ if (emptyArray && configThresholdsSize == 1) {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ if (emptyArray || configPermille.length != configThresholdsSize) {
+ throw new IllegalArgumentException(
+ "Brightness threshold arrays do not align in length");
+ }
+
+ // Calculate levels array
+ float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+ // Start at 1, so that 0 index value is 0.0f (default)
+ for (int i = 1; i < configThresholdsSize; i++) {
+ configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+ }
+ if (potentialOldBrightnessScale) {
+ configThresholdWithZeroPrefixed =
+ constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+ }
+
+ // Calculate percentages array
+ float[] configPercentage = new float[configThresholdsSize];
+ for (int i = 0; i < configPermille.length; i++) {
+ configPercentage[i] = configPermille[i] / 10.0f;
+ }
+ return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+ } else {
+ return new Pair<>(defaultLevels, defaultPercentage);
+ }
+ }
+
+ /**
+ * This check is due to historical reasons, where screen thresholdLevels used to be
+ * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+ * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+ * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+ */
+ private static float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+ if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+ /* maxValueInclusive= */ 1.0f)) {
+ return thresholdLevels;
+ }
+
+ Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+ float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+ for (int index = 0; thresholdLevels.length > index; ++index) {
+ thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+ }
+ return thresholdLevelsScaled;
+ }
+
+ private static boolean isAllInRange(float[] configArray, float minValueInclusive,
+ float maxValueInclusive) {
+ for (float v : configArray) {
+ if (v < minValueInclusive || v > maxValueInclusive) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 67d3fe995160..db83d4b76778 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -232,7 +232,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
if (!mRunning) {
return false;
}
- if (!getSessionInfos().isEmpty() || mIsManagerScanning) {
+ boolean bindDueToManagerScan =
+ mIsManagerScanning && Flags.enablePreventionOfManagerScansWhenNoAppsScan();
+ if (!getSessionInfos().isEmpty() || bindDueToManagerScan) {
// We bind if any manager is scanning (regardless of whether an app is scanning) to give
// the opportunity for providers to publish routing sessions that were established
// directly between the app and the provider (typically via AndroidX MediaRouter). See
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ec15ff35676c..aa71e054ddee 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -114,6 +114,7 @@ class MediaRouter2ServiceImpl {
};
private final Context mContext;
+ private final Looper mLooper;
private final UserManagerInternal mUserManagerInternal;
private final Object mLock = new Object();
private final AppOpsManager mAppOpsManager;
@@ -178,8 +179,9 @@ class MediaRouter2ServiceImpl {
Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
Manifest.permission.WATCH_APPOPS
})
- /* package */ MediaRouter2ServiceImpl(Context context) {
+ /* package */ MediaRouter2ServiceImpl(@NonNull Context context, @NonNull Looper looper) {
mContext = context;
+ mLooper = looper;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING);
@@ -1891,7 +1893,7 @@ class MediaRouter2ServiceImpl {
private UserRecord getOrCreateUserRecordLocked(int userId) {
UserRecord userRecord = mUserRecords.get(userId);
if (userRecord == null) {
- userRecord = new UserRecord(userId);
+ userRecord = new UserRecord(userId, mLooper);
mUserRecords.put(userId, userRecord);
userRecord.init();
if (isUserActiveLocked(userId)) {
@@ -1962,9 +1964,13 @@ class MediaRouter2ServiceImpl {
Set<String> mActivelyScanningPackages = Set.of();
final UserHandler mHandler;
- UserRecord(int userId) {
+ UserRecord(int userId, @NonNull Looper looper) {
mUserId = userId;
- mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
+ mHandler =
+ new UserHandler(
+ /* service= */ MediaRouter2ServiceImpl.this,
+ /* userRecord= */ this,
+ looper);
}
void init() {
@@ -2365,12 +2371,16 @@ class MediaRouter2ServiceImpl {
private boolean mRunning;
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
- UserHandler(@NonNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord) {
- super(Looper.getMainLooper(), null, true);
+ UserHandler(
+ @NonNull MediaRouter2ServiceImpl service,
+ @NonNull UserRecord userRecord,
+ @NonNull Looper looper) {
+ super(looper, /* callback= */ null, /* async= */ true);
mServiceRef = new WeakReference<>(service);
mUserRecord = userRecord;
- mSystemProvider = new SystemMediaRoute2Provider(service.mContext,
- UserHandle.of(userRecord.mUserId));
+ mSystemProvider =
+ new SystemMediaRoute2Provider(
+ service.mContext, UserHandle.of(userRecord.mUserId), looper);
mRouteProviders.add(mSystemProvider);
mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
this, mUserRecord.mUserId);
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 76b8db685f52..4bdca29d3bd0 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -110,6 +110,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private static final long CONNECTED_TIMEOUT = 60000;
private final Context mContext;
+ private final Looper mLooper;
// State guarded by mLock.
private final Object mLock = new Object();
@@ -141,7 +142,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
@RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
public MediaRouterService(Context context) {
- mService2 = new MediaRouter2ServiceImpl(context);
+ mLooper = Looper.getMainLooper();
+ mService2 = new MediaRouter2ServiceImpl(context, mLooper);
mContext = context;
Watchdog.getInstance().addMonitor(this);
Resources res = context.getResources();
@@ -1104,7 +1106,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
public UserRecord(int userId) {
mUserId = userId;
- mHandler = new UserHandler(MediaRouterService.this, this);
+ mHandler = new UserHandler(MediaRouterService.this, this, mLooper);
}
public void dump(final PrintWriter pw, String prefix) {
@@ -1212,8 +1214,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
private long mConnectionTimeoutStartTime;
private boolean mClientStateUpdateScheduled;
- public UserHandler(MediaRouterService service, UserRecord userRecord) {
- super(Looper.getMainLooper(), null, true);
+ private UserHandler(MediaRouterService service, UserRecord userRecord, Looper looper) {
+ super(looper, null, true);
mService = service;
mUserRecord = userRecord;
mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 67bc61c3b9f6..802acba2e412 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -86,12 +86,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
@GuardedBy("mTransferLock")
@Nullable private volatile SessionCreationRequest mPendingTransferRequest;
- SystemMediaRoute2Provider(Context context, UserHandle user) {
+ SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) {
super(COMPONENT_NAME);
mIsSystemRouteProvider = true;
mContext = context;
mUser = user;
- Looper looper = Looper.getMainLooper();
mHandler = new Handler(looper);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index b3e5b9e96889..43e2afd8827d 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -2,9 +2,7 @@
"presubmit": [
{
"name": "CtsMediaBetterTogetherTestCases"
- }
- ],
- "postsubmit": [
+ },
{
"name": "MediaRouterServiceTests"
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 87b17692fefc..2a3b939c5295 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
import static android.content.pm.PackageManager.APP_METADATA_SOURCE_INSTALLER;
+import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -2210,6 +2211,7 @@ final class InstallPackageHelper {
Map<String, PackageManager.Property> properties = parsedPackage.getProperties();
if (Flags.aslInApkAppMetadataSource()
&& properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ // ASL file extraction is done in post-install
ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
ps.setAppMetadataSource(APP_METADATA_SOURCE_APK);
} else {
@@ -2809,6 +2811,20 @@ final class InstallPackageHelper {
}
if (succeeded) {
+ if (Flags.aslInApkAppMetadataSource()
+ && pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
+ if (!PackageManagerServiceUtils.extractAppMetadataFromApk(request.getPkg(),
+ pkgSetting.getAppMetadataFilePath())) {
+ synchronized (mPm.mLock) {
+ PackageSetting setting = mPm.mSettings.getPackageLPr(packageName);
+ if (setting != null) {
+ setting.setAppMetadataFilePath(null)
+ .setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
+ }
+ }
+ }
+ }
+
// Clear the uid cache after we installed a new package.
mPm.mPerUidReadTimeoutsCache = null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72cd447bb3cd..68cd3e463905 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -19,8 +19,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.admin.flags.Flags.crossUserSuspensionEnabledRo;
-import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK;
-import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -5232,26 +5230,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return null;
}
File file = new File(filePath);
- if (Flags.aslInApkAppMetadataSource() && !file.exists()
- && ps.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
- AndroidPackageInternal pkg = ps.getPkg();
- if (pkg == null) {
- Slog.w(TAG, "Unable to to extract app metadata for " + packageName
- + ". APK missing from device");
- return null;
- }
- if (!PackageManagerServiceUtils.extractAppMetadataFromApk(pkg, file)) {
- if (file.exists()) {
- file.delete();
- }
- synchronized (mLock) {
- PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
- pkgSetting.setAppMetadataFilePath(null);
- pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
- }
- return null;
- }
- }
try {
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 110a29c4ee58..9484d0d7b52b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1570,7 +1570,12 @@ public class PackageManagerServiceUtils {
/**
* Extract the app.metadata file from apk.
*/
- public static boolean extractAppMetadataFromApk(AndroidPackage pkg, File appMetadataFile) {
+ public static boolean extractAppMetadataFromApk(AndroidPackage pkg,
+ String appMetadataFilePath) {
+ if (appMetadataFilePath == null) {
+ return false;
+ }
+ File appMetadataFile = new File(appMetadataFilePath);
Map<String, Property> properties = pkg.getProperties();
if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
return false;
@@ -1596,6 +1601,7 @@ public class PackageManagerServiceUtils {
}
} catch (Exception e) {
Slog.e(TAG, e.getMessage());
+ appMetadataFile.delete();
}
}
return false;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 63386a999d40..ff41245e1e13 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1719,10 +1719,11 @@ public class UserManagerService extends IUserManager.Stub {
}
final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
- if (km != null && km.isDeviceSecure()) {
+ int parentUserId = getProfileParentId(userId);
+ if (km != null && km.isDeviceSecure(parentUserId)) {
showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
- } else if (km != null && !km.isDeviceSecure()
+ } else if (km != null && !km.isDeviceSecure(parentUserId)
&& android.multiuser.Flags.showSetScreenLockDialog()
// TODO(b/330720545): Add a better way to accomplish this, also use it
// to block profile creation w/o device credentials present.
@@ -1732,7 +1733,8 @@ public class UserManagerService extends IUserManager.Stub {
SetScreenLockDialogActivity
.createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
- mContext.startActivity(setScreenLockPromptIntent);
+ mContext.startActivityAsUser(setScreenLockPromptIntent,
+ UserHandle.of(parentUserId));
return false;
} else {
Slog.w(LOG_TAG, "Allowing profile unlock even when device credentials "
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 76bf8fd45a43..7db83d7dcc6f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5664,6 +5664,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ @Override
+ public void onDisplaySwitchStart(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ mDefaultDisplayPolicy.onDisplaySwitchStart();
+ }
+ }
+
private long getKeyguardDrawnTimeout() {
final boolean bootCompleted =
LocalServices.getService(SystemServiceManager.class).isBootCompleted();
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 5956594acd26..2623025cacf1 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -895,6 +895,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
void onScreenOff();
}
+ /** Called when the physical display starts to switch, e.g. fold/unfold. */
+ void onDisplaySwitchStart(int displayId);
+
/**
* Return whether the default display is on and not blocked by a black surface.
*/
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index a29cb60ff545..ca5f26aa4cc8 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -26,6 +26,10 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.os.Message;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Slog;
import android.view.DisplayInfo;
import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
@@ -35,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater;
+import com.android.window.flags.Flags;
import java.util.Arrays;
import java.util.Objects;
@@ -65,6 +70,12 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
WM_OVERRIDE_FIELDS.setFields(out, override);
};
+ private static final String TAG = "DeferredDisplayUpdater";
+
+ private static final String TRACE_TAG_WAIT_FOR_TRANSITION =
+ "Screen unblock: wait for transition";
+ private static final int WAIT_FOR_TRANSITION_TIMEOUT = 1000;
+
private final DisplayContent mDisplayContent;
@NonNull
@@ -88,6 +99,18 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
@NonNull
private final DisplayInfo mOutputDisplayInfo = new DisplayInfo();
+ /** Whether {@link #mScreenUnblocker} should wait for transition to be ready. */
+ private boolean mShouldWaitForTransitionWhenScreenOn;
+
+ /** The message to notify PhoneWindowManager#finishWindowsDrawn. */
+ @Nullable
+ private Message mScreenUnblocker;
+
+ private final Runnable mScreenUnblockTimeoutRunnable = () -> {
+ Slog.e(TAG, "Timeout waiting for the display switch transition to start");
+ continueScreenUnblocking();
+ };
+
public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) {
mDisplayContent = displayContent;
mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
@@ -248,6 +271,7 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
getCurrentDisplayChange(fromRotation, startBounds);
displayChange.setPhysicalDisplayChanged(true);
+ transition.addTransactionCompletedListener(this::continueScreenUnblocking);
mDisplayContent.mTransitionController.requestStartTransition(transition,
/* startTask= */ null, /* remoteTransition= */ null, displayChange);
@@ -277,6 +301,58 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
return !Objects.equals(first.uniqueId, second.uniqueId);
}
+ @Override
+ public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation,
+ DisplayAreaInfo newDisplayAreaInfo) {
+ // Unblock immediately in case there is no transition. This is unlikely to happen.
+ if (mScreenUnblocker != null && !mDisplayContent.mTransitionController.inTransition()) {
+ mScreenUnblocker.sendToTarget();
+ mScreenUnblocker = null;
+ }
+ }
+
+ @Override
+ public void onDisplaySwitching(boolean switching) {
+ mShouldWaitForTransitionWhenScreenOn = switching;
+ }
+
+ @Override
+ public boolean waitForTransition(@NonNull Message screenUnblocker) {
+ if (!Flags.waitForTransitionOnDisplaySwitch()) return false;
+ if (!mShouldWaitForTransitionWhenScreenOn) {
+ return false;
+ }
+ mScreenUnblocker = screenUnblocker;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.beginAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, screenUnblocker.hashCode());
+ }
+
+ mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
+ mDisplayContent.mWmService.mH.postDelayed(mScreenUnblockTimeoutRunnable,
+ WAIT_FOR_TRANSITION_TIMEOUT);
+ return true;
+ }
+
+ /**
+ * Continues the screen unblocking flow, could be called either on a binder thread as
+ * a result of surface transaction completed listener or from {@link WindowManagerService#mH}
+ * handler in case of timeout
+ */
+ private void continueScreenUnblocking() {
+ synchronized (mDisplayContent.mWmService.mGlobalLock) {
+ mShouldWaitForTransitionWhenScreenOn = false;
+ mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable);
+ if (mScreenUnblocker == null) {
+ return;
+ }
+ mScreenUnblocker.sendToTarget();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.endAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, mScreenUnblocker.hashCode());
+ }
+ mScreenUnblocker = null;
+ }
+ }
+
/**
* Diff result: fields are the same
*/
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 54abbc3f6dd5..cde3e68e43c9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -470,7 +470,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final DisplayRotation mDisplayRotation;
@Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
DisplayFrames mDisplayFrames;
- private final DisplayUpdater mDisplayUpdater;
+ final DisplayUpdater mDisplayUpdater;
private boolean mInTouchMode;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 16f7373ebc5e..a5037ea0ff07 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -779,6 +779,11 @@ public class DisplayPolicy {
return mLidState;
}
+ private void onDisplaySwitchFinished() {
+ mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(false);
+ }
+
public void setAwake(boolean awake) {
synchronized (mLock) {
if (awake == mAwake) {
@@ -797,7 +802,7 @@ public class DisplayPolicy {
mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
if (!awake) {
- mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ onDisplaySwitchFinished();
}
}
}
@@ -866,7 +871,7 @@ public class DisplayPolicy {
/** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */
public void screenTurnedOn() {
- mDisplayContent.mWallpaperController.onDisplaySwitchFinished();
+ onDisplaySwitchFinished();
}
public void screenTurnedOff() {
@@ -2187,6 +2192,11 @@ public class DisplayPolicy {
mDisplayContent.mTransitionController.getCollectingTransitionId();
}
+ /** If this is called, expect that there will be an onDisplayChanged about unique id. */
+ public void onDisplaySwitchStart() {
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(true);
+ }
+
@NavigationBarPosition
int navigationBarPosition(int displayRotation) {
if (mNavigationBar != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java
index e611177210e8..918b180ab1cb 100644
--- a/services/core/java/com/android/server/wm/DisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DisplayUpdater.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.NonNull;
+import android.os.Message;
import android.view.Surface;
import android.window.DisplayAreaInfo;
@@ -49,4 +50,16 @@ interface DisplayUpdater {
@Surface.Rotation int previousRotation, @Surface.Rotation int newRotation,
@NonNull DisplayAreaInfo newDisplayAreaInfo) {
}
+
+ /**
+ * Called with {@code true} when physical display is going to switch. And {@code false} when
+ * the display is turned on or the device goes to sleep.
+ */
+ default void onDisplaySwitching(boolean switching) {
+ }
+
+ /** Returns {@code true} if the transition will control when to turn on the screen. */
+ default boolean waitForTransition(@NonNull Message screenUnBlocker) {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3779d9eef727..1b380aadee35 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -112,6 +112,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
/**
@@ -233,6 +234,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
*/
private ArrayList<Task> mTransientHideTasks;
+ @VisibleForTesting
+ ArrayList<Runnable> mTransactionCompletedListeners = null;
+
/** Custom activity-level animation options and callbacks. */
private TransitionInfo.AnimationOptions mOverrideOptions;
private IRemoteCallback mClientAnimationStartCallback = null;
@@ -1640,6 +1644,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
commitVisibleActivities(transaction);
commitVisibleWallpapers();
+ if (mTransactionCompletedListeners != null) {
+ for (int i = 0; i < mTransactionCompletedListeners.size(); i++) {
+ final Runnable listener = mTransactionCompletedListeners.get(i);
+ transaction.addTransactionCompletedListener(Runnable::run,
+ (stats) -> listener.run());
+ }
+ }
+
// Fall-back to the default display if there isn't one participating.
final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
: mController.mAtm.mRootWindowContainer.getDefaultDisplay();
@@ -1862,6 +1874,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
/**
+ * Adds a listener that will be executed after the start transaction of this transition
+ * is presented on the screen, the listener will be executed on a binder thread
+ */
+ void addTransactionCompletedListener(Runnable listener) {
+ if (mTransactionCompletedListeners == null) {
+ mTransactionCompletedListeners = new ArrayList<>();
+ }
+ mTransactionCompletedListeners.add(listener);
+ }
+
+ /**
* Checks if the transition contains order changes.
*
* This is a shallow check that doesn't account for collection in parallel, unlike
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6762e7a29b0b..2e721210d685 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -49,6 +49,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
+import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -3427,7 +3428,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
throw new SecurityException("Requires CONTROL_KEYGUARD permission");
}
- if (mAtmService.mKeyguardController.isShowingDream()) {
+ if (!dreamHandlesConfirmKeys() && mAtmService.mKeyguardController.isShowingDream()) {
mAtmService.mTaskSupervisor.wakeUp("leaveDream");
}
synchronized (mGlobalLock) {
@@ -8076,6 +8077,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
boolean allWindowsDrawn = false;
synchronized (mGlobalLock) {
+ if (mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
+ // Use the ready-to-play of transition as the signal.
+ return;
+ }
container.waitForAllWindowsDrawn();
mWindowPlacerLocked.requestTraversal();
mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index 82f9aadba9f4..d24afabe95a4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -92,7 +92,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type == XmlPullParser.START_TAG
&& parser.getName().equals(TAG_VALUE)) {
- values.add(parser.nextText().trim());
+ values.add(parser.nextText());
count--;
}
}
@@ -111,7 +111,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
restrictions.putParcelableArray(key,
bundleList.toArray(new Bundle[bundleList.size()]));
} else {
- String value = parser.nextText().trim();
+ String value = parser.nextText();
if (ATTR_TYPE_BOOLEAN.equals(valType)) {
restrictions.putBoolean(key, Boolean.parseBoolean(value));
} else if (ATTR_TYPE_INTEGER.equals(valType)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b34092ca280f..3dd7b5480da1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11509,10 +11509,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setApplicationRestrictions(ComponentName who, String callerPackage,
- String packageName, Bundle restrictions) {
+ String packageName, Bundle restrictions, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
+ // This check is eventually made in UMS, checking here to fail early.
+ String validationResult =
+ FrameworkParsingPackageUtils.validateName(packageName, false, false);
+ if (validationResult != null) {
+ throw new IllegalArgumentException("Invalid package name: " + validationResult);
+ }
+
if (isUnicornFlagEnabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
who,
@@ -11520,12 +11527,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getPackageName(),
caller.getUserId()
);
- // This check is eventually made in UMS, checking here to fail early.
- String validationResult =
- FrameworkParsingPackageUtils.validateName(packageName, false, false);
- if (validationResult != null) {
- throw new IllegalArgumentException("Invalid package name: " + validationResult);
- }
if (restrictions == null || restrictions.isEmpty()) {
mDevicePolicyEngine.removeLocalPolicy(
@@ -11541,6 +11542,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
setBackwardsCompatibleAppRestrictions(
caller, packageName, restrictions, caller.getUserHandle());
+ } else if (Flags.dmrhCanSetAppRestriction()) {
+ final boolean isRoleHolder;
+ if (who != null) {
+ // DO or PO
+ Preconditions.checkCallAuthorization(
+ (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ Preconditions.checkCallAuthorization(!parent,
+ "DO or PO cannot call this on parent");
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Delegates, or the DMRH. Only DMRH can call this on COPE parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ // DMRH caller uses policy engine, others still use legacy code path
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ if (restrictions == null || restrictions.isEmpty()) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ enforcingAdmin,
+ affectedUserId);
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ enforcingAdmin,
+ new BundlePolicyValue(restrictions),
+ affectedUserId);
+ }
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
+ } else {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ mUserManager.setApplicationRestrictions(packageName, restrictions,
+ caller.getUserHandle());
+ });
+ }
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
@@ -12872,7 +12924,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
- String packageName) {
+ String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
if (isUnicornFlagEnabled()) {
@@ -12891,6 +12943,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
+ } else if (Flags.dmrhCanSetAppRestriction()) {
+ final boolean isRoleHolder;
+ if (who != null) {
+ // Caller is DO or PO. They cannot call this on parent
+ Preconditions.checkCallAuthorization(!parent
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)));
+ // Caller has opted to be treated as DPC (by passing a non-null who), so don't
+ // consider it as the DMRH, even if the caller is both the DPC and the DMRH.
+ isRoleHolder = false;
+ } else {
+ // Caller is delegates or the DMRH. Only DMRH can call this on parent
+ isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller);
+ if (parent) {
+ Preconditions.checkCallAuthorization(isRoleHolder);
+ Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(),
+ "Role Holder can only operate parent app restriction on COPE devices");
+ } else {
+ Preconditions.checkCallAuthorization(isRoleHolder
+ || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ }
+ }
+ if (isRoleHolder) {
+ EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null,
+ caller.getPackageName());
+ int affectedUserId = parent
+ ? getProfileParentId(caller.getUserId()) : caller.getUserId();
+ LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
+ mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+ PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+ affectedUserId);
+ if (!policies.containsKey(enforcingAdmin)) {
+ return Bundle.EMPTY;
+ }
+ return policies.get(enforcingAdmin).getValue();
+ } else {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
+ caller.getUserHandle());
+ // if no restrictions were saved, mUserManager.getApplicationRestrictions
+ // returns null, but DPM method should return an empty Bundle as per JavaDoc
+ return bundle != null ? bundle : Bundle.EMPTY;
+ });
+ }
+
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
@@ -15811,19 +15907,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
for (EnforcingAdmin admin : policies.keySet()) {
restrictions.add(policies.get(admin).getValue());
}
- if (!restrictions.isEmpty()) {
- return restrictions;
- }
return mInjector.binderWithCleanCallingIdentity(() -> {
- // Could be a device that has a DPC that hasn't migrated yet, so just return any
+ // Could be a device that has a DPC that hasn't migrated yet, so also return any
// restrictions saved in userManager.
Bundle bundle = mUserManager.getApplicationRestrictions(
packageName, UserHandle.of(userId));
- if (bundle == null || bundle.isEmpty()) {
- return new ArrayList<>();
+ if (bundle != null && !bundle.isEmpty()) {
+ restrictions.add(bundle);
}
- return List.of(bundle);
+ return restrictions;
});
}
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index 00bfcd3007a4..4de4a56aa806 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -103,6 +103,28 @@
"include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
}
]
+ },
+ {
+ "name": "CtsVirtualDevicesAudioTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsVirtualDevicesAppLaunchTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest"
+ }
+ ]
}
],
"imports": [
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54de64e2f3a8..64253e10bd21 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
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 494a6677e633..b80d44fb8fd8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -169,53 +170,57 @@ public final class DisplayDeviceConfigTest {
assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
// Test thresholds
- assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
- ZERO_DELTA);
- assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
- ZERO_DELTA);
- assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
- assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
- assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+ HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+ HysteresisLevels ambientIdleHysteresis =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+ HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+ HysteresisLevels screenIdleHysteresis =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
+ assertEquals(10, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(20, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(30, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(40, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
+
+ assertEquals(0.1f, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0.2f, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0.3f, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0.4f, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.10f, 0.20f},
- mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{9, 10, 11},
- mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ screenHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.09f, 0.10f, 0.11f},
+ screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.11f, 0.21f},
- mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{11, 12, 13},
- mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+ screenHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.11f, 0.12f, 0.13f},
+ screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 100, 200},
- mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{13, 14, 15},
- mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.13f, 0.14f, 0.15f},
+ ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 300, 400},
- mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{15, 16, 17},
- mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.15f, 0.16f, 0.17f},
+ ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.12f, 0.22f},
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{17, 18, 19},
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.17f, 0.18f, 0.19f},
+ screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 0.13f, 0.23f},
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{19, 20, 21},
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.19f, 0.20f, 0.21f},
+ screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 500, 600},
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{21, 22, 23},
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.21f, 0.22f, 0.23f},
+ ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 700, 800},
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{23, 24, 25},
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.23f, 0.24f, 0.25f},
+ ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
@@ -686,55 +691,60 @@ public final class DisplayDeviceConfigTest {
new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
brightnessIntToFloat(150)}, SMALL_DELTA);
+ HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+ HysteresisLevels ambientIdleHysteresis =
+ mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+ HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+ HysteresisLevels screenIdleHysteresis =
+ mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
// Test thresholds
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
- ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
- assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+ assertEquals(0, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+ assertEquals(0, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+ assertEquals(0, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
// screen levels will be considered "old screen brightness scale"
// and therefore will divide by 255
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
- assertArrayEquals(new float[]{35, 36, 37},
- mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+ screenHysteresis.getBrighteningThresholdLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+ screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
- assertArrayEquals(new float[]{37, 38, 39},
- mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+ screenHysteresis.getDarkeningThresholdLevels(), SMALL_DELTA);
+ assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+ screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{27, 28, 29},
- mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+ ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
- assertArrayEquals(new float[]{29, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+ ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+ ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
- assertArrayEquals(new float[]{35, 36, 37},
- mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+ screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
- mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
- assertArrayEquals(new float[]{37, 38, 39},
- mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+ screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+ screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{27, 28, 29},
- mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+ ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
assertArrayEquals(new float[]{0, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
- assertArrayEquals(new float[]{29, 30, 31},
- mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+ ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+ assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+ ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index afb87d1df798..de939146f68e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -82,6 +82,7 @@ import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.HysteresisLevels;
import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
@@ -1954,6 +1955,14 @@ public final class DisplayPowerControllerTest {
.thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE);
when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis())
.thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
+
+ final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+ when(displayDeviceConfigMock.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(displayDeviceConfigMock.getAmbientBrightnessIdleHysteresis()).thenReturn(
+ hysteresisLevels);
+ when(displayDeviceConfigMock.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(displayDeviceConfigMock.getScreenBrightnessIdleHysteresis()).thenReturn(
+ hysteresisLevels);
}
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
@@ -1995,7 +2004,7 @@ public final class DisplayPowerControllerTest {
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController,
+ screenOffBrightnessSensorController,
hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
@@ -2005,6 +2014,11 @@ public final class DisplayPowerControllerTest {
final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+ when(config.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getAmbientBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+ when(config.getScreenBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
@@ -2080,7 +2094,6 @@ public final class DisplayPowerControllerTest {
private final AutomaticBrightnessController mAutomaticBrightnessController;
private final WakelockController mWakelockController;
private final BrightnessMappingStrategy mBrightnessMappingStrategy;
- private final HysteresisLevels mHysteresisLevels;
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
@@ -2096,7 +2109,6 @@ public final class DisplayPowerControllerTest {
AutomaticBrightnessController automaticBrightnessController,
WakelockController wakelockController,
BrightnessMappingStrategy brightnessMappingStrategy,
- HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
NormalBrightnessModeController normalBrightnessModeController,
@@ -2108,7 +2120,6 @@ public final class DisplayPowerControllerTest {
mAutomaticBrightnessController = automaticBrightnessController;
mWakelockController = wakelockController;
mBrightnessMappingStrategy = brightnessMappingStrategy;
- mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
mNormalBrightnessModeController = normalBrightnessModeController;
@@ -2186,22 +2197,6 @@ public final class DisplayPowerControllerTest {
}
@Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold) {
- return mHysteresisLevels;
- }
-
- @Override
- HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
- float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
- float[] darkeningThresholdLevels, float minDarkeningThreshold,
- float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
- return mHysteresisLevels;
- }
-
- @Override
ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
SensorManager sensorManager, Sensor lightSensor, Handler handler,
ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
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 5a50510082d6..1a03e780521a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -79,12 +79,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.foldables.FoldGracePeriodProvider;
+import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.server.LocalServices;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.policy.WindowManagerPolicy;
import com.android.server.utils.FoldSettingProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -124,6 +128,9 @@ public class LogicalDisplayMapperTest {
private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock FoldSettingProvider mFoldSettingProviderMock;
@@ -133,6 +140,7 @@ public class LogicalDisplayMapperTest {
@Mock IThermalService mIThermalServiceMock;
@Mock DisplayManagerFlags mFlagsMock;
@Mock DisplayAdapter mDisplayAdapterMock;
+ @Mock WindowManagerPolicy mWindowManagerPolicy;
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@Captor ArgumentCaptor<Integer> mDisplayEventCaptor;
@@ -143,6 +151,9 @@ public class LogicalDisplayMapperTest {
System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
+ mLocalServiceKeeperRule.overrideLocalService(WindowManagerPolicy.class,
+ mWindowManagerPolicy);
+
mDeviceStateToLayoutMapSpy =
spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE));
mDisplayDeviceRepo = new DisplayDeviceRepository(
@@ -194,6 +205,7 @@ public class LogicalDisplayMapperTest {
mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
mDeviceStateToLayoutMapSpy, mFlagsMock);
+ mLogicalDisplayMapper.onWindowManagerReady();
}
@@ -757,6 +769,44 @@ public class LogicalDisplayMapperTest {
}
@Test
+ public void testDisplaySwappedAfterDeviceStateChange_windowManagerIsNotified() {
+ FoldableDisplayDevices foldableDisplayDevices = createFoldableDeviceStateToLayoutMap();
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN);
+ mLogicalDisplayMapper.onEarlyInteractivityChange(true);
+ mLogicalDisplayMapper.onBootCompleted();
+ advanceTime(1000);
+ clearInvocations(mWindowManagerPolicy);
+
+ // Switch from 'inner' to 'outer' display (fold a foldable device)
+ mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED);
+ // Continue folding device state transition by turning off the inner display
+ foldableDisplayDevices.mInner.setState(STATE_OFF);
+ notifyDisplayChanges(foldableDisplayDevices.mOuter);
+ advanceTime(TIMEOUT_STATE_TRANSITION_MILLIS);
+
+ verify(mWindowManagerPolicy).onDisplaySwitchStart(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testCreateNewLogicalDisplay_windowManagerIsNotNotifiedAboutSwitch() {
+ DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+ LogicalDisplay display1 = add(device1);
+
+ assertTrue(display1.isEnabledLocked());
+
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
+ add(device2);
+
+ // As it is not a display switch but adding a new display, we should not notify
+ // about display switch start to window manager
+ verify(mWindowManagerPolicy, never()).onDisplaySwitchStart(anyInt());
+ }
+
+ @Test
public void testDoNotWaitForSleepWhenFoldSettingStayAwake() {
// Test device should be marked ready for transition immediately when 'Continue using app
// on fold' set to 'Always'
diff --git a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
index c379d6b79ee7..3fd3cef07dd5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
@@ -43,6 +43,11 @@ public class NormalBrightnessModeControllerTest {
private final NormalBrightnessModeController mController = new NormalBrightnessModeController();
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // NormalBrightnessModeController is temporary disabled if auto brightness is off,
+ // to avoid capping brightness based on stale ambient lux. Temporary disabling tests with
+ // auto brightness off and default config pres
+ // The issue is tracked here: b/322445088
@Keep
private static Object[][] brightnessData() {
return new Object[][]{
@@ -59,10 +64,10 @@ public class NormalBrightnessModeControllerTest {
ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
), 0.2f},
// Auto brightness - off, config only for default
- {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
- BrightnessLimitMapType.DEFAULT,
- ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
- ), 0.2f},
+ // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ // BrightnessLimitMapType.DEFAULT,
+ // ImmutableMap.of(99f, 0.1f, 101f, 0.2f)
+ // ), 0.2f},
// Auto brightness - off, config only for adaptive
{100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
BrightnessLimitMapType.ADAPTIVE,
@@ -81,12 +86,12 @@ public class NormalBrightnessModeControllerTest {
ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
), 0.4f},
// Auto brightness - off, config for both
- {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
- BrightnessLimitMapType.DEFAULT,
- ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
- BrightnessLimitMapType.ADAPTIVE,
- ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
- ), 0.2f},
+ // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of(
+ // BrightnessLimitMapType.DEFAULT,
+ // ImmutableMap.of(99f, 0.1f, 101f, 0.2f),
+ // BrightnessLimitMapType.ADAPTIVE,
+ // ImmutableMap.of(99f, 0.3f, 101f, 0.4f)
+ // ), 0.2f},
// Auto brightness - on, config for both, ambient high
{1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of(
BrightnessLimitMapType.DEFAULT,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 87fc7a484c5c..39ffe5be5882 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -33,6 +33,7 @@ import android.os.PowerManager;
import androidx.test.filters.SmallTest;
+import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.config.HdrBrightnessData;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -230,6 +231,11 @@ public class HdrClamperTest {
}
private void configureClamper() {
+ // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
+ // HdrClamper is temporary disabled if auto brightness is off.
+ // Temporary setting AutoBrightnessState to enabled for this test
+ // The issue is tracked here: b/322445088
+ mHdrClamper.setAutoBrightnessState(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder);
mHdrChangeListener.onHdrVisible(true);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907af93c..671472d619d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1978,7 +1978,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2021,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2167,73 @@ public class QuotaControllerTest {
}
@Test
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+ setDischarging();
+
+ JobStatus jobRunning = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+ JobStatus jobPending = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+ setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+ long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+ final ExecutionStats stats;
+ synchronized (mQuotaController.mLock) {
+ stats = mQuotaController.getExecutionStatsLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ assertTrue(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.jobCountLimit);
+ assertEquals(9, stats.bgJobCountInWindow);
+ }
+
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+ trackJobs(jobRunning, jobPending);
+ // UID in the background.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobRunning);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Wait for some extra time to allow for job processing.
+ ArraySet<JobStatus> expected = new ArraySet<>();
+ expected.add(jobPending);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(eq(expected));
+
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+ assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobRunning.isReady());
+ assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+ assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobPending.isReady());
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+ }
+
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+ }
+
+ @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4718,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6618,7 +6685,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
index dae8f932cb91..09462290fb4f 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -1,23 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.contentcapture"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_contentcapture"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
index 32729a899a96..1ad7baa09529 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -1,23 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.contentprotection"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_contentprotection"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index dc8f934ff9e5..58f5bb3eb7d0 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -1,29 +1,11 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.location.contexthub."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ "name": "FrameworksServicesTests_contexthub_presubmit"
}
],
"postsubmit": [
{
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_contexthub_presubmit"
- },
- {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 41c4383a0bec..944c1df94b92 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -1,12 +1,7 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.om."
- }
- ]
+ "name": "FrameworksServicesTests_om"
},
{
"name": "PackageManagerServiceHostTests",
@@ -16,11 +11,5 @@
}
]
}
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_om"
- }
]
}
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 06e7002924a7..2138da9478d7 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -1,17 +1,6 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.os."
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
"name": "FrameworksServicesTests_os"
}
]
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index f4e724f493c5..861562d11f10 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -21,7 +21,7 @@
"postsubmit": [
{
// Presubmit is intentional here while testing with SLO checker.
- // b/331020193, Move to presubmit early april 2024
+ // Tests are flaky, waiting to bypass.
"name": "FrameworksServicesTests_pm_presubmit"
},
{
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 7e7393c3a822..eb7453d5b86a 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -1,21 +1,7 @@
{
- "presubmit": [
- {
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.recoverysystem."
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // b/331020193, Move to presubmit early april 2024
- "name": "FrameworksServicesTests_recoverysystem"
- }
- ]
-} \ No newline at end of file
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests_recoverysystem"
+ }
+ ]
+}
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 b11f9b2306df..073b55165c9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.when;
+import android.os.Message;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
@@ -60,6 +61,8 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
private int mColorMode;
private int mLogicalDensityDpi;
+ private final Message mScreenUnblocker = mock(Message.class);
+
@Override
protected void onBeforeSystemServicesCreated() {
// Set other flags to their default values
@@ -73,12 +76,11 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
doReturn(true).when(mDisplayContent).getLastHasContent();
mockTransitionsController(/* enabled= */ true);
mockRemoteDisplayChangeController();
+ performInitialDisplayUpdate();
}
@Test
public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
- performInitialDisplayUpdate();
-
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -107,8 +109,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() {
- performInitialDisplayUpdate();
-
// Update only color mode (non-deferrable field) and keep the same unique id
mUniqueId = "initial_unique_id";
mColorMode = 123;
@@ -121,8 +121,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() {
- performInitialDisplayUpdate();
-
// Update only color mode (non-deferrable field) and keep the same unique id
mUniqueId = "initial_unique_id";
mColorMode = 123;
@@ -163,7 +161,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
- performInitialDisplayUpdate();
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -181,7 +178,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
@Test
public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
- performInitialDisplayUpdate();
mUniqueId = "old";
Runnable onUpdated = mock(Runnable.class);
mDisplayContent.requestDisplayUpdate(onUpdated);
@@ -212,6 +208,51 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2");
}
+ @Test
+ public void testWaitForTransition_displaySwitching_waitsForTransitionToBeStarted() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+ assertThat(willWait).isTrue();
+ mUniqueId = "new";
+ mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
+ when(mDisplayContent.mTransitionController.inTransition()).thenReturn(true);
+ captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+
+ // Verify that screen is not unblocked yet as the start transaction hasn't been presented
+ verify(mScreenUnblocker, never()).sendToTarget();
+
+ when(mDisplayContent.mTransitionController.inTransition()).thenReturn(false);
+ final Transition transition = captureRequestedTransition().getValue();
+ makeTransitionTransactionCompleted(transition);
+
+ // Verify that screen is unblocked as start transaction of the transition
+ // has been completed
+ verify(mScreenUnblocker).sendToTarget();
+ }
+
+ @Test
+ public void testWaitForTransition_displayNotSwitching_doesNotWait() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ false);
+
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+
+ assertThat(willWait).isFalse();
+ verify(mScreenUnblocker, never()).sendToTarget();
+ }
+
+ @Test
+ public void testWaitForTransition_displayIsSwitchingButFlagDisabled_doesNotWait() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
+ mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
+
+ boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker);
+
+ assertThat(willWait).isFalse();
+ verify(mScreenUnblocker, never()).sendToTarget();
+ }
+
private void mockTransitionsController(boolean enabled) {
spyOn(mDisplayContent.mTransitionController);
when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
@@ -233,6 +274,23 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
return callbackCaptor;
}
+ private ArgumentCaptor<Transition> captureRequestedTransition() {
+ ArgumentCaptor<Transition> callbackCaptor =
+ ArgumentCaptor.forClass(Transition.class);
+ verify(mDisplayContent.mTransitionController, atLeast(1))
+ .requestStartTransition(callbackCaptor.capture(), any(), any(), any());
+ return callbackCaptor;
+ }
+
+ private void makeTransitionTransactionCompleted(Transition transition) {
+ if (transition.mTransactionCompletedListeners != null) {
+ for (int i = 0; i < transition.mTransactionCompletedListeners.size(); i++) {
+ final Runnable listener = transition.mTransactionCompletedListeners.get(i);
+ listener.run();
+ }
+ }
+ }
+
private void performInitialDisplayUpdate() {
mUniqueId = "initial_unique_id";
mColorMode = 0;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 1233686a4b48..00a8842c358e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -167,6 +167,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
+ public void onDisplaySwitchStart(int displayId) {
+ }
+
+ @Override
public boolean okToAnimate(boolean ignoreScreenOn) {
return mOkToAnimate;
}
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 3aebd70f2a21..ec2c968a8a0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -245,15 +245,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- public void testDismissKeyguardCanWakeUp() {
- doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString());
- doReturn(true).when(mWm.mAtmService.mKeyguardController).isShowingDream();
- doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
- mWm.dismissKeyguard(null, "test-dismiss-keyguard");
- verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
- }
-
- @Test
public void testTrackOverlayWindow() {
final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
"pkgName", "processName", 1000 /* pid */, Process.SYSTEM_UID);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9b5612eacc13..9470c0a944c2 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -60,6 +60,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
@@ -222,6 +223,21 @@ public class UsbService extends IUsbManager.Stub {
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
}
+ // Ideally we should use the injector pattern so we wouldn't need this constructor for test
+ @VisibleForTesting
+ UsbService(Context context,
+ UsbPortManager usbPortManager,
+ UsbAlsaManager usbAlsaManager,
+ UserManager userManager,
+ UsbSettingsManager usbSettingsManager) {
+ mContext = context;
+ mPortManager = usbPortManager;
+ mAlsaManager = usbAlsaManager;
+ mUserManager = userManager;
+ mSettingsManager = usbSettingsManager;
+ mPermissionManager = new UsbPermissionManager(context, this);
+ }
+
/**
* Set new {@link #mCurrentUserId} and propagate it to other modules.
*
@@ -886,7 +902,16 @@ public class UsbService extends IUsbManager.Stub {
@Override
public boolean enableUsbData(String portId, boolean enable, int operationId,
- IUsbOperationInternal callback) {
+ IUsbOperationInternal callback) {
+ return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+ }
+
+ /**
+ * Internal function abstracted for testing with callerUid
+ */
+ @VisibleForTesting
+ boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
+ IUsbOperationInternal callback, int callerUid) {
Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -894,7 +919,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, enable, Binder.getCallingUid())) {
+ if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
@@ -950,7 +975,16 @@ public class UsbService extends IUsbManager.Stub {
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
- IUsbOperationInternal callback) {
+ IUsbOperationInternal callback) {
+ enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+ }
+
+ /**
+ * Internal function abstracted for testing with callerUid
+ */
+ @VisibleForTesting
+ void enableUsbDataWhileDockedInternal(String portId, int operationId,
+ IUsbOperationInternal callback, int callerUid) {
Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+ operationId);
Objects.requireNonNull(callback,
@@ -959,7 +993,7 @@ public class UsbService extends IUsbManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
- if (!shouldUpdateUsbSignaling(portId, true, Binder.getCallingUid())) {
+ if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
try {
callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
} catch (RemoteException e) {
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 86eed2f6a090..ec60c676d078 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -793,7 +793,7 @@ public final class TelephonyPermissions {
if (isGranted) return;
if (allowCarrierPrivilegeOnAnySub) {
- if (checkCarrierPrivilegeForAnySubId(context, Binder.getCallingUid())) return;
+ if (checkCarrierPrivilegeForAnySubId(context, uid)) return;
} else {
if (checkCarrierPrivilegeForSubId(context, subId)) return;
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index a63db88cb614..7b5b07c0fbf6 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony.util;
import static android.telephony.Annotation.DataState;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +39,7 @@ import android.provider.Telephony.Carriers.EditStatus;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
@@ -48,6 +51,8 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* This class provides various util functions
@@ -342,4 +347,31 @@ public final class TelephonyUtils {
return false;
}
+
+ /**
+ * @param plmn target plmn for validation.
+ * @return {@code true} if the target plmn is valid {@code false} otherwise.
+ */
+ public static boolean isValidPlmn(@Nullable String plmn) {
+ if (TextUtils.isEmpty(plmn)) {
+ return false;
+ }
+ Pattern pattern = Pattern.compile("^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
+ Matcher matcher = pattern.matcher(plmn);
+ if (!matcher.matches()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param serviceType target serviceType for validation.
+ * @return {@code true} if the target serviceType is valid {@code false} otherwise.
+ */
+ public static boolean isValidService(int serviceType) {
+ if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 36485c6b6fb5..16983a0dbca1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -83,6 +83,9 @@ oneway interface ISatellite {
*
* @param enableSatellite True to enable the satellite modem and false to disable.
* @param enableDemoMode True to enable demo mode and false to disable.
+ * @param isEmergency To specify the satellite is enabled for emergency session and false for
+ * non emergency session. Note: it is possible that a emergency session started get converted
+ * to a non emergency session and vice versa.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned:
@@ -96,7 +99,7 @@ oneway interface ISatellite {
* SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
*/
void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
- in IIntegerConsumer resultCallback);
+ in boolean isEmergency, in IIntegerConsumer resultCallback);
/**
* Request to get whether the satellite modem is enabled.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index b7dc79ff7283..a62363335fb9 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -90,11 +90,11 @@ public class SatelliteImplBase extends SatelliteService {
@Override
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
- IIntegerConsumer resultCallback) throws RemoteException {
+ boolean isEmergency, IIntegerConsumer resultCallback) throws RemoteException {
executeMethodAsync(
() -> SatelliteImplBase.this
.requestSatelliteEnabled(
- enableSatellite, enableDemoMode, resultCallback),
+ enableSatellite, enableDemoMode, isEmergency, resultCallback),
"requestSatelliteEnabled");
}
@@ -337,6 +337,9 @@ public class SatelliteImplBase extends SatelliteService {
*
* @param enableSatellite True to enable the satellite modem and false to disable.
* @param enableDemoMode True to enable demo mode and false to disable.
+ * @param isEmergency To specify the satellite is enabled for emergency session and false for
+ * non emergency session. Note: it is possible that a emergency session started get converted
+ * to a non emergency session and vice versa.
* @param resultCallback The callback to receive the error code result of the operation.
*
* Valid result codes returned:
@@ -350,7 +353,7 @@ public class SatelliteImplBase extends SatelliteService {
* SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
*/
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
- @NonNull IIntegerConsumer resultCallback) {
+ boolean isEmergency, @NonNull IIntegerConsumer resultCallback) {
// stub implementation
}
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
index a62103e0030b..755833234e02 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -16,10 +16,16 @@
package com.android.internal.telephony.tests;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -72,6 +78,22 @@ public class TelephonyUtilsTest {
// getSubscriptionUserHandle should be called if subID is active.
verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
}
+
+ @Test
+ public void testIsValidPlmn() {
+ assertTrue(TelephonyUtils.isValidPlmn("310260"));
+ assertTrue(TelephonyUtils.isValidPlmn("45006"));
+ assertFalse(TelephonyUtils.isValidPlmn("1234567"));
+ assertFalse(TelephonyUtils.isValidPlmn("1234"));
+ }
+
+ @Test
+ public void testIsValidService() {
+ assertTrue(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE));
+ assertTrue(TelephonyUtils.isValidService(LAST_SERVICE_TYPE));
+ assertFalse(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE - 1));
+ assertFalse(TelephonyUtils.isValidService(LAST_SERVICE_TYPE + 1));
+ }
}
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index a16a7eafc8e8..f0bea3f3c28a 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -21,6 +21,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_usb",
}
android_test {
diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp
index 92c271165ad7..c012cce494e2 100644
--- a/tests/UsbTests/Android.bp
+++ b/tests/UsbTests/Android.bp
@@ -21,6 +21,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_usb",
}
android_test {
@@ -36,6 +37,8 @@ android_test {
"services.usb",
"truth",
"UsbManagerTestLib",
+ "android.hardware.usb.flags-aconfig-java",
+ "flag-junit",
],
jni_libs: [
// Required for ExtendedMockito
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
new file mode 100644
index 000000000000..b506d74d6500
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.flags.Flags;
+import android.os.RemoteException;
+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 androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.usb.UsbService}
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbServiceTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UsbPortManager mUsbPortManager;
+ @Mock
+ private UsbAlsaManager mUsbAlsaManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UsbSettingsManager mUsbSettingsManager;
+ @Mock
+ private IUsbOperationInternal mIUsbOperationInternal;
+
+ private static final String TEST_PORT_ID = "123";
+
+ private static final int TEST_TRANSACTION_ID = 1;
+
+ private static final int TEST_FIRST_CALLER_ID = 1000;
+
+ private static final int TEST_SECOND_CALLER_ID = 2000;
+
+ private UsbService mUsbService;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager,
+ mUsbSettingsManager);
+ }
+
+ /**
+ * Verify enableUsbData successfully disables USB port without error
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPort_SuccessfullyDisabled() {
+ boolean enableState = false;
+ when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null)).thenReturn(true);
+
+ assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
+
+ verify(mUsbPortManager, times(1)).enableUsbData(TEST_PORT_ID,
+ enableState, TEST_TRANSACTION_ID, mIUsbOperationInternal, null);
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+
+ /**
+ * Verify enableUsbData successfully enables USB port without error given no other stakers
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPortWhenNoOtherStakers_SuccessfullyEnabledUsb() {
+ boolean enableState = true;
+ when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null))
+ .thenReturn(true);
+
+ assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID));
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+
+ /**
+ * Verify enableUsbData does not enable USB port if other stakers are present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void usbPortWithOtherStakers_DoesNotToEnableUsb() throws RemoteException {
+ mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+ clearInvocations(mUsbPortManager);
+
+ assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, true,
+ TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_SECOND_CALLER_ID));
+
+ verifyZeroInteractions(mUsbPortManager);
+ verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+
+ /**
+ * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void enableUsbWhileDockedWhenThereAreOtherStakers_DoesNotEnableUsb()
+ throws RemoteException {
+ mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_FIRST_CALLER_ID);
+
+ mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, 0,
+ mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+
+ verify(mUsbPortManager, never()).enableUsbDataWhileDocked(any(),
+ anyLong(), any(), any());
+ verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+ }
+
+ /**
+ * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
+ * not present
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING)
+ public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnableUsb()
+ throws RemoteException {
+ mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, TEST_SECOND_CALLER_ID);
+
+ verify(mUsbPortManager, times(1))
+ .enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
+ mIUsbOperationInternal, null);
+ verifyZeroInteractions(mIUsbOperationInternal);
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 30333da5e86c..682adbc86d06 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -82,13 +82,30 @@ java_library {
jarjar_rules: "jarjar-rules.txt",
}
+// For sharing the code with other tools
+java_library_host {
+ name: "hoststubgen-lib",
+ defaults: ["ravenwood-internal-only-visibility-java"],
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "hoststubgen-helper-runtime",
+ ],
+ libs: [
+ "junit",
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "ow2-asm-util",
+ ],
+}
+
// Host-side stub generator tool.
java_binary_host {
name: "hoststubgen",
- main_class: "com.android.hoststubgen.Main",
- srcs: ["src/**/*.kt"],
+ main_class: "com.android.hoststubgen.HostStubGenMain",
static_libs: [
- "hoststubgen-helper-runtime",
+ "hoststubgen-lib",
"junit",
"ow2-asm",
"ow2-asm-analysis",
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 1089f82b6472..803dc283b8c7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -32,7 +32,6 @@ import com.android.hoststubgen.visitors.PackageRedirectRemapper
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.FileOutputStream
@@ -52,7 +51,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val stats = HostStubGenStats()
// Load all classes.
- val allClasses = loadClassStructures(options.inJar.get)
+ val allClasses = ClassNodes.loadClassStructures(options.inJar.get)
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
@@ -92,55 +91,6 @@ class HostStubGen(val options: HostStubGenOptions) {
}
/**
- * Load all the classes, without code.
- */
- private fun loadClassStructures(inJar: String): ClassNodes {
- log.i("Reading class structure from $inJar ...")
- val start = System.currentTimeMillis()
-
- val allClasses = ClassNodes()
-
- log.withIndent {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
-
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- if (entry.name.endsWith(".class")) {
- val cr = ClassReader(bis)
- val cn = ClassNode()
- cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES)
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file. It contains a *.dex file.")
- } else {
- // Unknown file type. Skip.
- while (bis.available() > 0) {
- bis.skip((1024 * 1024).toLong())
- }
- }
- }
- }
- }
- }
- if (allClasses.size == 0) {
- log.w("$inJar contains no *.class files.")
- }
-
- val end = System.currentTimeMillis()
- log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
- return allClasses
- }
-
- /**
* Build the filter, which decides what classes/methods/fields should be put in stub or impl
* jars, and "how". (e.g. with substitution?)
*/
@@ -229,7 +179,7 @@ class HostStubGen(val options: HostStubGenOptions) {
val intersectingJars = mutableMapOf<String, ClassNodes>()
filenames.forEach { filename ->
- intersectingJars[filename] = loadClassStructures(filename)
+ intersectingJars[filename] = ClassNodes.loadClassStructures(filename)
}
return intersectingJars
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 4882c00d2b3c..45e7e301c0d1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:JvmName("Main")
+@file:JvmName("HostStubGenMain")
package com.android.hoststubgen
import java.io.PrintWriter
-const val COMMAND_NAME = "HostStubGen"
-
/**
* Entry point.
*/
fun main(args: Array<String>) {
+ executableName = "HostStubGen"
+
var success = false
var clanupOnError = false
@@ -33,7 +33,7 @@ fun main(args: Array<String>) {
val options = HostStubGenOptions.parseArgs(args)
clanupOnError = options.cleanUpOnError.get
- log.v("HostStubGen started")
+ log.v("$executableName started")
log.v("Options: $options")
// Run.
@@ -41,7 +41,7 @@ fun main(args: Array<String>) {
success = true
} catch (e: Throwable) {
- log.e("$COMMAND_NAME: Error: ${e.message}")
+ log.e("$executableName: Error: ${e.message}")
if (e !is UserErrorException) {
e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
}
@@ -49,7 +49,7 @@ fun main(args: Array<String>) {
TODO("Remove output jars here")
}
} finally {
- log.i("$COMMAND_NAME finished")
+ log.i("$executableName finished")
log.flush()
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 9f5d524517d0..9ff798a4b5cb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -268,7 +268,7 @@ class HostStubGenOptions(
}
if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
- " $COMMAND_NAME will not generate jar files.")
+ " $executableName will not generate jar files.")
}
if (ret.enableNonStubMethodCallDetection.get) {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
index 937e56c2cbb5..aa63d8d9f870 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -16,6 +16,11 @@
package com.android.hoststubgen
/**
+ * Name of this executable. Set it in the main method.
+ */
+var executableName = "[command name not set]"
+
+/**
* A regex that maches whitespate.
*/
val whitespaceRegex = """\s+""".toRegex()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 0579c2bb0525..83e122feeeb2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -34,6 +34,9 @@ val CLASS_INITIALIZER_NAME = "<clinit>"
/** Descriptor of the class initializer method. */
val CLASS_INITIALIZER_DESC = "()V"
+/** Name of constructors. */
+val CTOR_NAME = "<init>"
+
/**
* Find any of [anyAnnotations] from the list of visible / invisible annotations.
*/
@@ -135,10 +138,10 @@ fun writeByteCodeToPushArguments(
// Note, long and double will consume two local variable spaces, so the extra `i++`.
when (type) {
Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitVarInsn(Opcodes.ILOAD, i)
- Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
+ Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
else -> writer.visitVarInsn(Opcodes.ALOAD, i)
}
@@ -154,10 +157,10 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
// See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
when (type) {
Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
- Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+ Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE
-> writer.visitInsn(Opcodes.IRETURN)
- Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
+ Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
else -> writer.visitInsn(Opcodes.ARETURN)
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index bc34ef0dc8a7..92906a75b93a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -16,13 +16,18 @@
package com.android.hoststubgen.asm
import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.InvalidJarFileException
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TypeAnnotationNode
+import java.io.BufferedInputStream
import java.io.PrintWriter
import java.util.Arrays
+import java.util.zip.ZipFile
/**
* Stores all classes loaded from a jar file, in a form of [ClassNode]
@@ -62,8 +67,8 @@ class ClassNodes {
/** Find a field, which may not exist. */
fun findField(
- className: String,
- fieldName: String,
+ className: String,
+ fieldName: String,
): FieldNode? {
return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
return fn
@@ -72,14 +77,14 @@ class ClassNodes {
/** Find a method, which may not exist. */
fun findMethod(
- className: String,
- methodName: String,
- descriptor: String,
+ className: String,
+ methodName: String,
+ descriptor: String,
): MethodNode? {
return findClass(className)?.methods
- ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
- return mn
- }
+ ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+ return mn
+ }
}
/** @return true if a class has a class initializer. */
@@ -106,26 +111,33 @@ class ClassNodes {
private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
- dumpAnnotations(pw, " ",
- cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
- cn.visibleAnnotations, cn.invisibleAnnotations,
- )
+ dumpAnnotations(
+ pw, " ",
+ cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
+ cn.visibleAnnotations, cn.invisibleAnnotations,
+ )
for (f in cn.fields ?: emptyList()) {
- pw.printf(" Field: %s [sig: %s] [desc: %s] [access: %x]\n",
- f.name, f.signature, f.desc, f.access)
- dumpAnnotations(pw, " ",
- f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
- f.visibleAnnotations, f.invisibleAnnotations,
- )
+ pw.printf(
+ " Field: %s [sig: %s] [desc: %s] [access: %x]\n",
+ f.name, f.signature, f.desc, f.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
+ f.visibleAnnotations, f.invisibleAnnotations,
+ )
}
for (m in cn.methods ?: emptyList()) {
- pw.printf(" Method: %s [sig: %s] [desc: %s] [access: %x]\n",
- m.name, m.signature, m.desc, m.access)
- dumpAnnotations(pw, " ",
- m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
- m.visibleAnnotations, m.invisibleAnnotations,
- )
+ pw.printf(
+ " Method: %s [sig: %s] [desc: %s] [access: %x]\n",
+ m.name, m.signature, m.desc, m.access
+ )
+ dumpAnnotations(
+ pw, " ",
+ m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
+ m.visibleAnnotations, m.invisibleAnnotations,
+ )
}
}
@@ -136,7 +148,7 @@ class ClassNodes {
invisibleTypeAnnotations: List<TypeAnnotationNode>?,
visibleAnnotations: List<AnnotationNode>?,
invisibleAnnotations: List<AnnotationNode>?,
- ) {
+ ) {
for (an in visibleTypeAnnotations ?: emptyList()) {
pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
}
@@ -166,4 +178,55 @@ class ClassNodes {
}
}
}
+
+ companion object {
+ /**
+ * Load all the classes, without code.
+ */
+ fun loadClassStructures(inJar: String): ClassNodes {
+ log.i("Reading class structure from $inJar ...")
+ val start = System.currentTimeMillis()
+
+ val allClasses = ClassNodes()
+
+ log.withIndent {
+ ZipFile(inJar).use { inZip ->
+ val inEntries = inZip.entries()
+
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
+
+ BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ if (entry.name.endsWith(".class")) {
+ val cr = ClassReader(bis)
+ val cn = ClassNode()
+ cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES)
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
+ }
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$inJar is not a desktop jar file. It contains a *.dex file.")
+ } else {
+ // Unknown file type. Skip.
+ while (bis.available() > 0) {
+ bis.skip((1024 * 1024).toLong())
+ }
+ }
+ }
+ }
+ }
+ }
+ if (allClasses.size == 0) {
+ log.w("$inJar contains no *.class files.")
+ }
+
+ val end = System.currentTimeMillis()
+ log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+ return allClasses
+ }
+ }
} \ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 78b13fd36f06..5a26fc69d473 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -19,14 +19,14 @@ import com.android.hoststubgen.HostStubGenErrors
import com.android.hoststubgen.HostStubGenInternalException
import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
-import com.android.hoststubgen.asm.isAnonymousInnerClass
-import com.android.hoststubgen.log
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.isAnnotation
+import com.android.hoststubgen.asm.isAnonymousInnerClass
import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
import com.android.hoststubgen.asm.isEnum
import com.android.hoststubgen.asm.isSynthetic
import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.log
import org.objectweb.asm.tree.ClassNode
/**
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index f70a17d9b7cd..fa8fe6cd384f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1833,7 +1833,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 2
+ interfaces: 0, fields: 1, methods: 11, attributes: 2
int value;
descriptor: I
flags: (0x0000)
@@ -1938,6 +1938,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V
x: athrow
LineNumberTable:
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
}
SourceFile: "TinyFrameworkNative.java"
RuntimeInvisibleAnnotations:
@@ -1955,7 +1959,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 2
+ interfaces: 0, fields: 0, methods: 5, attributes: 2
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2013,6 +2017,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
Start Length Slot Name Signature
0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
0 7 1 arg I
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 arg1 B
+ 0 5 1 arg2 B
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index c9c607c58c68..11d5939b7917 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -2236,7 +2236,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 10, attributes: 3
+ interfaces: 0, fields: 1, methods: 11, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -2435,6 +2435,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -2457,7 +2474,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 4, attributes: 3
+ interfaces: 0, fields: 0, methods: 5, attributes: 3
public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
@@ -2551,6 +2568,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 15 5 0 arg1 B
+ 15 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 37de857b9780..c605f767f527 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 9, attributes: 3
+ interfaces: 0, fields: 1, methods: 10, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static native byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index a57907d9398b..088bc80e11c5 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2743,7 +2743,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 11, attributes: 3
+ interfaces: 0, fields: 1, methods: 12, attributes: 3
int value;
descriptor: I
flags: (0x0000)
@@ -3002,6 +3002,28 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: iload_1
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+ x: ireturn
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative.java"
RuntimeVisibleAnnotations:
@@ -3024,7 +3046,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 5, attributes: 3
+ interfaces: 0, fields: 0, methods: 6, attributes: 3
private static {};
descriptor: ()V
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
@@ -3148,6 +3170,36 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static byte nativeBytePlus(byte, byte);
+ descriptor: (BB)B
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+ x: ldc #x // String nativeBytePlus
+ x: ldc #x // String (BB)B
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+ x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+ x: iload_0
+ x: iload_1
+ x: iadd
+ x: i2b
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 26 5 0 arg1 B
+ 26 5 1 arg2 B
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
}
SourceFile: "TinyFrameworkNative_host.java"
RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index 5a5e22db59e5..09ee183a2dcc 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -52,4 +52,6 @@ public class TinyFrameworkNative {
public static void nativeStillNotSupported_should_be_like_this() {
throw new RuntimeException();
}
+
+ public static native byte nativeBytePlus(byte arg1, byte arg2);
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
index 749ebaa378e3..b23c21602967 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -34,4 +34,8 @@ public class TinyFrameworkNative_host {
public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) {
return source.value + arg;
}
+
+ public static byte nativeBytePlus(byte arg1, byte arg2) {
+ return (byte) (arg1 + arg2);
+ }
}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index ba17c75132f2..762180dcf74b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -154,13 +154,22 @@ public class TinyFrameworkClassTest {
}
@Test
+ public void testNativeSubstitutionLong() {
+ assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L);
+ }
+
+ @Test
+ public void testNativeSubstitutionByte() {
+ assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7);
+ }
+
+ @Test
public void testNativeSubstitutionClass_nonStatic() {
TinyFrameworkNative instance = new TinyFrameworkNative();
instance.setValue(5);
assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
}
-
@Test
public void testSubstituteNativeWithThrow() throws Exception {
// We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,